Good day.
I changed the system on my home server, and the software itself also needed to be rearranged.
Therefore, for the sake of the test, I looked through several of the most popular torrent clients running on * nix (rTorrent, Deluge, MLDonkey, Transmission).
I liked the latter the most, but for me there was a significant drawback - it is impossible to rename the names of the torrents sewn into the .torrent file.
That is, we will have all sorts of different folders on the disc, for example, “Krovavaja gora”, “Crime Scene New York”, or simply “Season 7”.
I don’t like it, I love order, respectively, I organize my film library (or rather its serial part) in the form of "% SERIAL_NAME% / Season N".
Transmission, alas, does not allow this. But since basically everything was fine, I undertook to customize the client for myself.
Transmission. Rename
The first subtask is the ability to rename the folder with the contents of the torrent in the file system, and continue to work correctly.
I was not the first to visit such a simple idea that it is necessary for the client. In bugtracking system there is a ticket three years ago
# 1220 . Unfortunately, the developers somehow sluggishly react to it, but comrade
juxda kindly wrote a
patch that adds this functionality to the samples.
However, the radius of curvature of my hands did not allow the patch to be correctly even applied to the revision (11895) for which it was made. In addition, I still wanted to have the latest version of the torrent client with this chip, because about 500 commits have passed since that revision.
Therefore, I chose the path of thoughtful patching, with an analysis of what we are doing specifically, so that you can correct it yourself if something happens, and finish clients to the demon.
Everything begins simply:
svn co svn://svn.transmissionbt.com/Transmission/trunk Transmission
We are taking the HEAD-revision from the SVN-server. The list of necessary packages is in the
truck , I note that if you do not need a gtk-client, then you can also not install some of the lib (libgtk2.0-dev, libnotify-dev, libglib2.0-dev can be played). libevent-dev needs fresh (2.0.10 at the moment), I had to compile from
source , since I could not find the -dev packages in the repository. The preparatory part is finished, you can go digging into the sources.
')
We start with the basics - the kernel (
libtransmission folder). Rule header -
transmission.h .
Add a field to the description structure of the torrent metadata
tr_info field "
char * rename ". This structure contains data obtained from .torrent. Actually, if there is no such data, then renaming is not possible, so it is logical to fill in this structure. The added field will contain the name of the torrent in the file system after the renaming.
In addition, we add a description of our new feature:
int tr_torrentRename( tr_torrent * torrent, const char * newname );
It's time to mention one difficulty - the file names are based on the original name.
Of course, you can patch everything up, but there are quite a few such places, and it’s better just to “reload” these names after downloading.
Therefore, we use the built-in data recovery mechanism - we add a field that contains this very list of paths to save, and when we boot we “remember” everything we need.
For now, let us postpone this moment for a while, but do not forget about it.
We turn to the main thing - the very function of moving. We will edit
torrent.h /
torrent.c .
First we add a function to the header file that will write to overwrite the paths for the files in the internal structure of the torrent; this function will be needed in the same mechanism:
void tr_torrentInitFileName( tr_torrent * tor, tr_file_index_t fileIndex, const char * name );
Well, in the
torrents.c itself her body:
void tr_torrentInitFileName( tr_torrent * tor, tr_file_index_t fileIndex, const char * name ) { tr_file * file; assert( tr_isTorrent( tor ) ); assert( fileIndex < tor->info.fileCount ); assert( name != NULL ); assert( name[0] != '\0' ); file = &tor->info.files[fileIndex]; tr_free( file->name ); file->name = tr_strdup( name ); }
Everything is primitive - we release the old path, write the new one. Such a small helper.
Next we find the
fileExists () function and after it we write the main code:
static bool dirExists( const char * path ) { struct stat sb; return stat( path, &sb ) == 0 && S_ISDIR( sb.st_mode ); } int tr_torrentRename( tr_torrent * tor, const char * newname ) { tr_info * info; const char * root, * p, * oldname, * base; char * oldpath = NULL, * newpath = NULL, * subpath = NULL; int err = 0; assert( tr_isTorrent( tor ) ); tr_torrentLock( tor ); if( !tr_torrentHasMetadata( tor ) ) { err = ENOENT; goto OUT; } if( !newname || !newname[0] || strchr( newname, TR_PATH_DELIMITER ) || !strcmp( newname, "." ) || !strcmp( newname, ".." ) ) { err = EINVAL; goto OUT; } info = &tor->info; if (info->rename) oldname = info->rename; else oldname = info->name; if( ( p = strchr( oldname, TR_PATH_DELIMITER ) ) ) { err = EISDIR; goto OUT; } if( !strcmp( newname, oldname ) ) goto OUT; root = tr_torrentGetCurrentDir( tor ); if( info->fileCount > 1 ) { tr_file_index_t fi; oldpath = tr_buildPath( root, oldname, NULL ); if( dirExists( oldpath ) ) { newpath = tr_buildPath( root, newname, NULL ); if( fileExists( newpath, NULL) ) { err = EEXIST; goto OUT; } if( rename( oldpath, newpath ) == -1 ) { err = errno; goto OUT; } } for( fi = 0; fi < info->fileCount; ++fi ) { tr_file * file = &info->files[fi]; char * newfnam; if( !( p = strchr( file->name, TR_PATH_DELIMITER ) ) ) continue; newfnam = tr_buildPath( newname, p + 1, NULL ); tr_free( file->name ); file->name = newfnam; } } else { if( tr_torrentFindFile2( tor, 0, &base, &subpath, NULL) ) { oldpath = tr_buildPath( base, subpath, NULL ); newpath = tr_buildPath( base, newname, NULL ); if( fileExists( newpath, NULL) ) { err = EEXIST; goto OUT; } if( rename( oldpath, newpath ) == -1 ) { err = errno; goto OUT; } } tr_free( info->files[0].name ); info->files[0].name = tr_strdup( newname ); } tr_free( info->rename ); if( !strcmp( newname, info->name ) ) info->rename = NULL; else info->rename = tr_strdup( newname ); tr_torrentSetDirty( tor ); OUT: if( err ) { const char * es = tr_strerror( err ), * fmt; if( oldpath && newpath ) { fmt = _( "Cannot rename \"%1$s\" to \"%2$s\": %3$s" ); tr_torerr( tor, fmt, oldpath, newpath, es ); } else if( oldpath ) { fmt = _( "Cannot rename \"%1$s\": %2$s" ); tr_torerr( tor, fmt, oldpath, es ); } else { fmt = _( "Cannot rename torrent: %s" ); tr_torerr( tor, fmt, es ); } } tr_torrentUnlock( tor ); tr_free( oldpath ); tr_free( newpath ); tr_free( subpath ); return err; }
Most of the code is simple checks, the
main part is actually the
rename () call itself, and editing
info-> files . Well, do not forget to fill
info-> rename .
Now you need to let everyone know that the name has changed. In fact, this can be done by direct edits. Despite the fact that the author of most of the code went the way of modifying
tr_torrentName (), I chose a different path. Modifying that function is only useful if you are going to use the gtk client, then yes, it is better to replace a single line of code with:
return tor->info.rename ? tor->info.rename : tor->info.name;
So that everything was in openwork for gtk, but since I do not use this gui, I thought it unnecessary to spoil a bunch of other things like building a magnet link (the original patch, of course, spoils). In fact, I need the
rename field only to build the path to the files, and in order to give via RPC, so that the RPC client can, for example, open the folder with the torrent. We have the first (so far half), the second is also easy to solve (go to the RPC implementation -
rpcimpl.c ).
We are looking for the
addField () function, which is responsible for the formation of torrent information fields for the answer. That is, we can request a certain set of fields about the torrent, and using this function, Transmission will generate this information. We are interested in the "
name " field; we replace the "
tr_torrentName (tor) " parameter with
tor->info.rename ? tor->info.rename : tor->info.name
Is done. Now RPC knows about our new status.
Since RPC has come to rule, then you need to add the rename command itself.
The interlayer function to insert before
torrentSet ():
static const char * renameTorrent( tr_torrent * tor, const char * str ) { int err = tr_torrentRename( tor, str ); return err == 0 ? NULL : tr_strerror( err ); }
Now we add the command itself, for this we edit the
torrentSet () function:
Add a variable description to the block -
const char * str ;
And check on the rename command:
if( !errmsg && tr_bencDictFindStr( args_in, "rename", &str ) ) errmsg = renameTorrent( tor, str );
What have we not done? And we forgot about the mechanism of saving the state of the torrent!
It is necessary to fill this gap, this module is contained in files (
resume.c /
resume.h ).
First, add the checkboxes for the saved fields. There is only one enumeration in the header file, it is difficult to get confused.
We will need to save information about the current location of the torrent (
inf-> rename ) and the list of files that I mentioned earlier.
So 2 checkboxes:
TR_FR_FILE_NAMES = ( 1 << 20 ), TR_FR_RENAME = ( 1 << 21 )
Despite the presence of the header file, there is a list of defay keys in resume.c (the key describes each state entity that is saved to disk).
We also need to “fit in” there:
#define KEY_FILE_NAMES "name" #define KEY_RENAME "rename"
The original "
name " field is not saved, as it is taken directly from .torrent. Therefore, we can use this identifier as a key, and this will not cause any confusion in the future.
Add path saving functions:
static void saveFileNames( tr_benc * dict, const tr_torrent * tor ) { const tr_info * inf = tr_torrentInfo( tor ); const tr_file_index_t n = inf->fileCount; tr_file_index_t i; tr_benc * list; list = tr_bencDictAddList( dict, KEY_FILE_NAMES, n ); for( i = 0; i < n; ++i ) tr_bencListAddStr( list, inf->files[i].name ); } static uint64_t loadFileNames( tr_benc * dict, tr_torrent * tor ) { uint64_t ret = 0; tr_info * inf = &tor->info; const tr_file_index_t n = inf->fileCount; tr_benc * list; if( tr_bencDictFindList( dict, KEY_FILE_NAMES, &list ) && tr_bencListSize( list ) == n ) { const char * name; tr_file_index_t i; for( i = 0; i < n; ++i ) if( tr_bencGetStr( tr_bencListChild( list, i ), &name ) ) tr_torrentInitFileName( tor, i, name ); ret = TR_FR_FILE_NAMES; } return ret; }
And we add to the interface functions our wish to save.
tr_torrentSaveResume () , immediately after checking
if (tr_torrentHasMetadata (tor)) :
if( tor->info.rename ) tr_bencDictAddStr( &top, KEY_RENAME, tor->info.rename ); saveFileNames( &top, tor );
And the load code in
loadFromFile ():
if( fieldsToLoad & TR_FR_FILE_NAMES ) fieldsLoaded |= loadFileNames( &top, tor ); if( ( fieldsToLoad & TR_FR_RENAME ) && tr_bencDictFindStr( &top, KEY_RENAME, &str ) && str && str[0] ) { tr_free( tor->info.rename ); tor->info.rename = tr_strdup( str ); }
It can be noted that we do not set these flags anywhere (TR_FR_ *), but only check them, how does the manager learn about what to load?
The answer is that the module uses the rule “everything is allowed, which is not prohibited”, that is, something like this:
flags & = ~ deniedFieilds;Boolean logic kindly tells us that our 20 and 21 bits will be set anyway.
In fact, that's all. In the patch indicated at the beginning of the topic there is the gtk and transmission-remote editing code, with the addition of this function, but everything is trivial there because these are actually simple clients redirecting requests to the server (directly to libtransmission for gtk, and through rpc for -remote) with a drop of business -logics.
Transmission. Display Name.
So, with the main problem figured out, now there is a second subtask.
I want to see in the client not a bunch of “Season N” as names (namely, they will be given to us by the rpc server, because, as I explained at the beginning of the topic, my torrents are stored according to this scheme), but quite meaningful lines. Therefore, we make a very small edit - just add a new property "
displayName " and a set of getters / setters in the RPC interface.
This is a very small and simple edit - we need to do the same thing as with the field
rename , only without business logic and with the modification of rpc-return.
The points:
- Add a field to the tr_torrent structure in transmission.h
- In the torrentSet () function, we add a simple modification of tor-> displayName using tr_strdup () and the " displayName " command, for example.
- Add a new field to addField ().
- The state manager also introduces a new key " displayName " with all the consequences.
When we change this field, it is necessary to mark the fact that the torrent is “dirty”, that is, its state has been changed since the last save.
In general, everything. You can compile.
./autogen.sh --disable-gtk
make
make install prefix=/usr
Now transmission has learned to perform this task, however, customers do not know about it.
But it does not matter. There are not so many modifications needed, and I have quite successfully corrected the dotNet Transmission GUI.
I do not think that with other clients will be more difficult.
Links