送信-パンを導入します

こんにちは

彼はホームサーバーでシステムを変更し、ソフトウェア自体も再配置する必要がありました。

したがって、テストのために、* nix(rTorrent、Deluge、MLDonkey、Transmission)で動作する最も人気のあるトレントクライアントのいくつかを調べました。

私は後者のすべてが一番好きでしたが、私にとっては大きなマイナスがありました-.torrentファイルに配線されたtorrent名の名前を変更することは不可能です。

つまり、ディスクにはさまざまな種類のフォルダーがあります。たとえば、「Krovavaja gora」、「Crime scene New York」、または「Season 7」だけです。

私はそれが好きではありません。注文が好きです。映画ライブラリー(またはその連続部分)を「%SERIAL_NAME%/ Season N」の形式で整理します。

悲しいかな、これは許可されていません。 しかし、基本的にすべてがうまくいったので、私はクライアントを自分用にカスタマイズすることを約束しました。



トランスミッション 名前を変更





最初のサブタスクは、ファイルシステム内のトレントの内容でフォルダーの名前を変更し、引き続き正常に機能する機能です。

私は、クライアントにとって必要であるという単純な考えに最初に訪れたのではありませんでした。 バグ追跡システムには、3年前のチケットがあります#1220 。 残念ながら、開発者はなんとかそれに対して緩慢に反応しますが、仲間のjuxdaはこの機能を追加するパッチを親切に書いてくれました。

しかし、私の手の曲率半径は、それが作成されたリビジョン(11895)でも正しくパッチを適用できませんでした。 さらに、このチップでは〜500のコミットがそのリビジョンから渡されたため、このチップでトレントクライアントの最新バージョンが必要でした。



そのため、私たちが何をしているかを正確に分析して、思慮深いパッチ適用のパスを選択しました。これにより、何かが発生した場合は自分で修正し、クライアントを悪魔で仕上げることさえできます。

すべては簡単に始まります:

svn co svn://svn.transmissionbt.com/Transmission/trunk Transmission





SVNサーバーからHEADリビジョンを取得します。 必要なパッケージのリストはトラックにあります。gtkクライアントが必要ない場合は、libsの一部を残すこともできます(libgtk2.0-dev、libnotify-dev、libglib2.0-devは失われる可能性があります)。 libevent-devには最新の(現時点では2.0.10)が必要です。リポジトリから-devパッケージが見つからなかったため、 sourceからコンパイルする必要がありました。 準備部分が終了したら、ソースを詳しく調べることができます。



基本から始めます-カーネル( libtransmissionフォルダー)。 header- transmission.hを編集します。

tor_infoメタデータ記述構造体tr_infoフィールド「 char * rename 」にフィールドを追加します。 この構造には、.torrentから取得したデータが含まれます。 実際、そのようなデータがない場合、名前の変更は不可能なので、この構造に適合するのは論理的です。追加されたフィールドには、名前変更後のファイルシステム内のトレントの名前が含まれます。



さらに、新しい関数の説明を追加します。

 int tr_torrentRename( tr_torrent * torrent, const char * newname );
      
      







1つの難しさを述べる時が来ました-ファイル名は元の名前に基づいて作成されます。

もちろん、すべてにパッチを当てることはできますが、そのような場所はたくさんあります。ロード後にこれらの名前を「オーバーロード」することをお勧めします。

したがって、組み込みの情報回復メカニズムを使用します。保存用のパスのこのリストを含むフィールドを追加し、ロードするときに必要なものをすべて「記憶」します。



この瞬間はしばらく延期しますが、忘れないでください。

主なものに移りましょう-移動機能そのものです。 torrent.h / torrent.cを編集します。

まず、トレントの内部構造にあるファイルの上書きパスを書き込む関数をヘッダーファイルに追加します。この関数は、まさにそのメカニズムで必要になります。

 void tr_torrentInitFileName( tr_torrent * tor, tr_file_index_t fileIndex, const char * name );
      
      





まあ、 torrents.c自体で、彼女の体:

 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 ); }
      
      





すべてが原始的です-古いパスをクリアし、新しいパスを作成します。 このような小さなヘルパー。

次に、 fileExists ()関数を見つけ、その後、メインコードを記述します。

 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 ) ) ) { /* Should not happen, but just in case. */ 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 ) { /* %1$s is the original file path. * %2$s is the new file path. * %3$s is the error message. */ fmt = _( "Cannot rename \"%1$s\" to \"%2$s\": %3$s" ); tr_torerr( tor, fmt, oldpath, newpath, es ); } else if( oldpath ) { /* %1$s is the existing file name. * %2$s is the error message. */ 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; }
      
      



* OLDNAME、*ベース。 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 ) ) ) { /* Should not happen, but just in case. */ 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 ) { /* %1$s is the original file path. * %2$s is the new file path. * %3$s is the error message. */ fmt = _( "Cannot rename \"%1$s\" to \"%2$s\": %3$s" ); tr_torerr( tor, fmt, oldpath, newpath, es ); } else if( oldpath ) { /* %1$s is the existing file name. * %2$s is the error message. */ 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; }



== -1) 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 ) ) ) { /* Should not happen, but just in case. */ 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 ) { /* %1$s is the original file path. * %2$s is the new file path. * %3$s is the error message. */ fmt = _( "Cannot rename \"%1$s\" to \"%2$s\": %3$s" ); tr_torerr( tor, fmt, oldpath, newpath, es ); } else if( oldpath ) { /* %1$s is the existing file name. * %2$s is the error message. */ 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; }





ほとんどのコードは単純なチェックであり、実質的な部分は実際のrename ()呼び出し自体であり、 info->ファイルが編集されます。 さて、 info-> renameを記入することを忘れないでください。



次に、名前が変更されたことを全員に知らせる必要があります。 実際、これは直接編集で行うことができます。 ほとんどのコードの作成者がtr_torrentName ()を変更するパスを取っているという事実にもかかわらず、私は別のパスを選択しました。 この関数の変更は、gtkクライアントを使用する場合にのみ役立ちます。そうであれば、1行のコードを次のコードで置き換えることをお勧めします。

 return tor->info.rename ? tor->info.rename : tor->info.name;
      
      





そのため、すべてがgtkの透かしの中にありましたが、このguiを使用しないため、マグネットリンクの構築(もちろん、元のパッチ)のような他のものを台無しにする必要はないと考えました。 実際、RPCクライアントがたとえばトレントでフォルダーを開くことができるように、ファイルへのパスを作成するため、およびRPC経由で送信するためにのみ名前変更フィールドが必要です。 最初のもの(今のところ半分)があり、2番目のものも簡単に解決できます(RPC実装の編集-rpcimpl.cに進みます )。



応答のトレント情報フィールドの形成を担当するaddField ()関数を探しています。 つまり、トレントに関するフィールドの特定のセットを要求でき、この関数Transmissionを使用してこの情報を生成します。 「 名前 」フィールドに興味があるので、パラメータ「 tr_torrentName(tor) 」を次のように置き換えます。

 tor->info.rename ? tor->info.rename : tor->info.name
      
      





できた これで、RPCは新しいステータスを認識します。

RPCの編集を開始したので、名前変更コマンド自体を追加する必要があります。

torrentSet ()の前に挿入されるレイヤー関数:

 static const char * renameTorrent( tr_torrent * tor, const char * str ) { int err = tr_torrentRename( tor, str ); return err == 0 ? NULL : tr_strerror( err ); }
      
      





このためにコマンド自体を追加し、 torrentSet ()関数を編集します。

変数の説明ブロックに追加-const char * str ;

そして、名前変更コマンドを確認します。

  if( !errmsg && tr_bencDictFindStr( args_in, "rename", &str ) ) errmsg = renameTorrent( tor, str );
      
      







私たちは何をしていませんか? そして、急流の状態を維持するメカニズムを忘れていました!

このギャップを埋める必要があります。このモジュールはファイルに含まれています( resume.c / resume.h )。

最初に、保存されたフィールドのフラグを追加します。 ヘッダーファイルには列挙が1つしかないため、混乱するのは困難です。

トレントの現在の場所に関する情報( inf-> rename )と前述のファイルのリストを保存する必要があります。

2つのチェックボックス:

  TR_FR_FILE_NAMES = ( 1 << 20 ), TR_FR_RENAME = ( 1 << 21 )
      
      





ヘッダーファイルが存在するにもかかわらず、 resume.cにはキー定義のリストがあります(キーはディスクに保存されている各状態エンティティを記述しています)。

そこで、「適合する」必要もあります。

 #define KEY_FILE_NAMES "name" #define KEY_RENAME "rename"
      
      





元の「 名前 」フィールドは、.torrentから直接取得されるため、保存されません。 したがって、この識別子をキーとして完全に使用できます。これにより、将来混乱が生じることはありません。

パス保存機能を追加します。

 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; }
      
      





インターフェース機能に保存の希望を追加します。

tr_torrentSaveResume()(tr_torrentHasMetadata(tor))をチェックした直後:

  if( tor->info.rename ) tr_bencDictAddStr( &top, KEY_RENAME, tor->info.rename ); saveFileNames( &top, tor );
      
      





そして、 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 ); }
      
      





これらのフラグをどこにも置かない(TR_FR_ *)ことに気づくかもしれませんが、それらをチェックするだけで、マネージャーはどのようにロードするかを知っていますか?

答えは、モジュールは「禁止されていないすべてが許可されている」というルール、つまり次のようなルールを使用していることです。flags&=〜deniedFieilds;

ブール論理は、とにかく20ビットと21ビットが設定されることを親切に教えてくれます。



これが実際にすべてです。 トピックの冒頭に示されているパッチには、編集コードgtkと送信リモートが含まれており、この関数が追加されていますが、これらは実際にはシンプルなクライアントであり、ビジネスのドロップでサーバーにリクエストをリダイレクトする(gtkの場合はlibtransmissionに、-remoteの場合はrpcを介して) -ロジック。



トランスミッション 表示名。



そこで、主な問題を整理しましたが、2番目のサブタスクが残っています。

クライアントには、名前として「シーズンN」の束ではなく(つまり、トピックの冒頭で説明したように、このスキームに従ってトレントが保存されるため、rpcサーバーによって提供されます)、むしろ意味のある行を確認します。 そのため、非常に小さな編集を行います。RPCインターフェースに新しい「 displayName 」プロパティとゲッター/セッターのセットを追加するだけです

これは非常に小さく簡単な編集です。 名前変更フィールドと同じことを行う必要がありますが、ビジネスロジックがなく、rpc-returnを変更するだけです。



ポイントについて:





このフィールドを変更する場合、トレントが「ダーティ」であること、つまり、最後の保存以降に状態が変更されたことをマークする必要があります。



一般的に、すべてのもの。 これは、コンパイルすることができます。



./autogen.sh --disable-gtk

make

make install prefix=/usr







現在、伝送はこのタスクを実行することを学びましたが、顧客はそれを知りません。

しかし、それは問題ではありません。 それほど多くの変更はなく、Transmission GUI dotNetの修正は非常に成功しました。

他のクライアントにとってこれ以上難しくなるとは思いません。



参照資料






All Articles