Posts in category truezip

DocFetcher 1.1.5 试用及 zip 文件中文问题解决

自从接触 DocFetcher 以来, 就一直使用它来搜索自己的各类文档, 当时也总结过这个软件存在的问题, 详见 桌面搜索工具 DocFetcher 试用笔记;

概况

最近发现 DocFetcher 已经发布了 1.1.5 版本, 根据网站上的介绍, 1.1 版本经过完全重写(rewritten from scratch), 增加了大量的新特征(详见 http://docfetcher.sourceforge.net/wiki/doku.php?id=changes_in_v1.1), 包括我在 桌面搜索工具 DocFetcher 试用笔记 中所关注的:

  • Archive indexing: DocFetcher can now traverse archives. The following archive formats are supported: zip and zip-derived formats, 7z, rar, SFX zip, SFX 7z - 实际实验确认可以支持搜索压缩包中的内容, 而且支持嵌套压缩, 具体可以查看下面的截图;
  • Indexing of and searching in filenames - 可以按照文件名查找;

经过简单试用, 觉得这个版本还是值得升级的, 随后发现在搜索 zip 格式压缩包里面的内容时, 中文文件名会变成乱码. 具体现象如下图所示, 虽然可以索引压缩文件中的内容, 但是 zip 格式压缩包中中文文件名显示为乱码, 而 7z 和 rar 格式则显示正确:

下载代码(git clone http://git.code.sf.net/p/docfetcher/code docfetcher-code)研究了一下, 发现 DocFetcher 使用了 truezip 来进行 zip 格式压缩文件的搜索(支持 jar|tar|tar.bz2|tar.gz|tb2|tbz|tgz|zip 等多种格式), 而在 truezip 中, zip 格式默认使用的字符集为 IBM437:

... ...
public class ZipDriver
extends FsCharsetArchiveDriver<ZipDriverEntry>
implements ZipOutputStreamParameters, ZipFileParameters<ZipDriverEntry> {

    private static final Logger logger = Logger.getLogger(
            ZipDriver.class.getName(),
            ZipDriver.class.getName());

    /**
     * The character set for entry names and comments in &quot;traditional&quot;
     * ZIP files, which is {@code "IBM437"}.
     */
    private static final Charset ZIP_CHARSET = Charset.forName("IBM437");

    private final IOPool<?> ioPool;

    /**
     * Constructs a new ZIP driver.
     * This constructor uses {@link #ZIP_CHARSET} for encoding entry names
     * and comments.
     *
     * @param ioPoolProvider the provider for I/O entry pools for allocating
     *        temporary I/O entries (buffers).
     */
    public ZipDriver(IOPoolProvider ioPoolProvider) {
        this(ioPoolProvider, ZIP_CHARSET);
    }
    ... ...

而日常我们使用的 zip 压缩文件都是使用本地字符集进行压缩的(在中文环境下, 一般就是GBK), 继续研究发现, DocFetcher 使用 truezipTFile, 实现与 java.io.File 相似的方式统一访问文件系统目录和压缩文件(也就是把压缩文件看作一个目录), 而 TFile 内部则通过一个 TArchiveDetector 类型的成员变量来依据后缀名确定使用那种 "Driver" 操作具体的压缩文件;

默认情况下, 通过 TConfig.getArchiveDetector() 得到的 ArchiveDetector 是 TArchiveDetector.ALL, 此时在 TArchiveDetector 中实际使用 FsDriverLocator.SINGLETON 来获得所有加载的 "Driver", FsDriverLocator 会通过 ServiceLocator 查找并加载所有可用的 Driver, zip 格式相关 Driver 的定义实现在 ZipDriverService 中:

@Immutable
public final class ZipDriverService extends FsDriverService {

    private static final Map<FsScheme, FsDriver>
            DRIVERS = newMap(new Object[][] {
                { "zip", new ZipDriver(IOPoolLocator.SINGLETON) },
                { "ear|jar|war", new JarDriver(IOPoolLocator.SINGLETON) },
                { "odt|ott|odg|otg|odp|otp|ods|ots|odc|otc|odi|oti|odf|otf|odm|oth|odb", new OdfDriver(IOPoolLocator.SINGLETON) },
                { "exe", new ReadOnlySfxDriver(IOPoolLocator.SINGLETON) },
            });

    @Override
    public Map<FsScheme, FsDriver> get() {
        return DRIVERS;
    }
}

从上面这段代码可见, 系统默认得到的 ZipDriver 使用的是默认字符集 IBM437, 所以, 会产生中文问题;

另外, 从源代码还可以看到, JarDriver 默认使用的字符集是 UTF-8;

解决这个问题的方式是对 ZipDriverService 进行一定的调整, 以便通过环境变量或者 Java 系统属性来调整 ZipDriver 的默认字符集, 然后把这个 class 以 jar 补丁的形式, 放到 CLASSPATH 的最前面. 修改后的 ZipDriverService 代码如下:

@Immutable
public final class ZipDriverService extends FsDriverService {
        private static final String SYS_PROP_ZIP_CHARSET = ZipDriverService.class.getPackage().getName() + ".ZIP_CHARSET";
        private static final String ENV_VAR_ZIP_CHARSET = "TRUEZIP_ZIP_CHARSET";

        private static final ZipDriver buildZipDriver(){
                String charset = System.getProperty(SYS_PROP_ZIP_CHARSET);
                if (null!=charset && charset.trim().length() > 0){
                        return new ZipDriver(
                                        IOPoolLocator.SINGLETON, Charset.forName(charset.trim()));
                }
                charset = System.getenv(ENV_VAR_ZIP_CHARSET);
                if (null!=charset && charset.trim().length() > 0){
                        return new ZipDriver(
                                        IOPoolLocator.SINGLETON, Charset.forName(charset.trim()));
                }
                
                return new ZipDriver(IOPoolLocator.SINGLETON);
        }

    private static final Map<FsScheme, FsDriver>
            DRIVERS = newMap(new Object[][] {
                { "zip", buildZipDriver() },
                { "ear|jar|war", new JarDriver(IOPoolLocator.SINGLETON) },
                { "odt|ott|odg|otg|odp|otp|ods|ots|odc|otc|odi|oti|odf|otf|odm|oth|odb", new OdfDriver(IOPoolLocator.SINGLETON) },
                { "exe", new ReadOnlySfxDriver(IOPoolLocator.SINGLETON) },
            });

    @Override
    public Map<FsScheme, FsDriver> get() {
        return DRIVERS;
    }
}

编译后的 jar 补丁可以到附件中下载, 此 jar 文件可以放到 DocFetcher-1.1.5 的 patches 目录下, 然后按照下图的样子修改 DocFetcher.sh:

修改后即可正常处理 zip 格式压缩文件中的中文文件名了:

总结

补充说明

本文所提供的补丁没有在 Windows 系统上进行测试, 如果要在 Windows 系统中使用, 建议设置系统环境变量 set TRUEZIP_ZIP_CHARSET=GBK 后运行, 效果应该与 Linux 下一致;

顺便说一下, DocFetcher 对 rar 文件的解析是通过 java-unrar 实现的, 与 zip 压缩文件不同, rar 和 7z 压缩文件是通过所谓 SolidArchiveFactory 来处理的, SolidArchive 模式需要将文件解压到临时目录后再进行索引处理;

END