解决 RTorrent 部分中文文件名乱码

这两天在我的 LS Pro 上装 BT 软件。以前我在 freebsd 下都是装的 ctorrent ,这次想换个别的。看中了 RTorrent ,项目维护还比较勤,新版支持 DHT 。不过 debian 的源上比较老,官网上那个源我连不上。所以就下源代码自己编译了一个。

原来以为这种小东西没多少代码量的,本地编译就够了。可惜我轻视了 C++ 代码编译的龟速。在 LS Pro 上,居然一个短短的 .cc 文件就要编译 20 秒左右。有点后悔没有用交叉编译,不过忍了几小时也就过去了。

新版本支持 xmlrpc 的控制协议。我就可以装一个 web 界面管理了 :) 我选的是 rtgui 还不错。

本来以为一切都搞定可以庆祝了的。没想到试了一个种子,其中中文文件名出现乱码,连带的导致了 rtgui 工作不正常 :( 然后花了一整夜的时间来折腾这个,好不辛苦。

按搜索来的信息,新版 rtorrent 在配置文件里加一行 encoding_list = utf-8 就可以支持中文。我试了以后,大多数种子都是正常的。但是少部分种子下载后不正确。

对比了两个种子文件,发现在种子文件头上有诸多文件名。而正确的文件名前都标有 utf-8 ,而出问题的文件名前是没有注明编码方式,而直接用的 GBK 编码。

我猜想 rtorrent 对于没有标明编码的字符串,一律不做任何转换,直接创建文件。通过阅读源码证实了这一点。唉,开源就是好啊,出了问题可以自己检查。当然了,在 rtorrent 华丽的圆环套圆环的 C++ 封装下,看透这个本质还是需要点时间和功力的,尤其是在我只有 vi 和 grep 等不多的命令行工具的情况下。

google 了老半天,愣是没一个亚洲人关心这个问题。通过阅读的源码,我确信这个问题是不能通过修改软件配置来解决的,因为 rtorrent 的源代码里,就没有任何编码转换的函数。

最终,我决定自己动手,丰衣足食。人家都开源了,就是鼓励你自己修改不满意的位置吧。因为我只想快点解决问题,而不是帮软件增强功能。所以我决定把补丁写死在代码里。遇到没有声明编码格式的文件名,通通从 GBK 转换为 UTF-8 。

rtorrent 是 C++ 写的,看起来很漂亮,一个类套一个类,层次分明,抽象的很好。当然也有不足的地方,就是光抽象,不好好干活,花了大量的代码做出一个个漂亮的对象,最终就干一点事情。不小心还弄点飞线出来用来直达目标,不过因为抽象层代码很多,所以飞线占的比例就自然很少啦。90% 的代码都是整洁的,10% 的代码又那么丁点坏味道。哦,对了,那 10% 的代码是真正干活的部分,做苦力的地方嘛,不用太注意干净了。

通过阅读 rtorrent 的代码,我又一次充分认识到:C++ 是怎么把每一件不起眼的小工作发挥的如此叹为观止,充分体现出一个高素质的 C++ 程序员的价值所在的 :)

下面说正题:经过一番分析,我确定了在 libtorrent 中,有个叫 Path 的类是用来处理文件名的。简单浏览了 path.h 文件,发现 Path 私有继承了一个 string 的 vector ,想必是保存了文件路径中的一段段字符串。既然是私有继承,我就比较放心了,只看 public 出来的接口就行。有个 as_string 的接口吸引了我,这个是看起来唯一可以取出 Path 内部字符串的接口。另外 m_encoding 变量也是 private 的,保存了编码方式。按我的预料,当 encoding 字符串为空的时候,就可以打我那个补丁了。

本地编译 C++ 文件实在是慢,所以我尽量不想修改 .h 文件改动接口。就从修改 path.cc 开始。在 as_string 里判断一下 encoding 是否为空,然后调用 iconv 转换编码。

只用了不到 10 分钟改完,重新编译一个文件,然后重新做试验。发现,问题依旧 :( 。不过似乎又和以前不太一样。我写了几行 log 重新测试,确认我的代码正常工作了。

这让我不得不怀疑,rtorrent 在 Path 类里跳了飞线。仔细研究了一下 .h 又 grep 了全部代码。我发现,Path 居然给出了 begin end 得方法,返回 private 继承的那个 vector.string 的 iterator 。而且,还有一个 public 方法叫 base ,可以返回 private 的基类。天哪,那个 private 继承不都暴露了,写上 private 只为了一个华丽的存在,来忽悠我这个曾经的 C++ 爱好者么?

进一步浏览代码,确信在创建目录时,它用了这根飞线,直接访问了 private 基类的数据。所以我的修改没完全生效。

经过一分钟的判断,我想我不改 .h 文件很难了。因为 C++ 要给 class 加一成员方法是不能像 C 那样另写在一处的。天啊,修改 path.h 会影响到几十个 .cc 文件重编译(在我的 LS Pro 上,这意味超过半小时的时间),虽然它们并不会用到我新加的方法。

最终我给 Path 加了一个在未指定编码时,把内部字符串转换为 utf-8 的成员方法。(理论上不增加这个方法也可以,可以在 Path 内部实现里加一个华丽的模块,可以自动转换,不过我放弃了这种做法)

最后修改一个叫 download_constructor.cc 的文件,源文件末尾实现的那个函数 DownloadConstructor::choose_path 里调用了一下新添加的转换函数。一切正常了。所有中文文件名显现出了它们应有的 utf-8 编码的和谐姿态。一切都是那么完美,除了源代码中那个难看的,一点都不 C++ 风格,一点都不华丽的补丁。


2009 年 1 月 30 日补充:

虽然上面已经说的很清楚了,但是依旧有朋友不想自己动手。我把 patch 放在这里好了。 不过这个解决方案绝对一点都不优雅,是我当时随手做来应付的。patch 上后,需要安装 iconv 才可以编译。

点击下载(libtorrent-0.12.3.patch)

不要再问我如何 patch 如何编译的细节。