Jul 16, 2009

nsIZipWriterを使ってFirefoxでZIPアーカイブを作る

nsIZipWriterを使えばアドオンでZIPアーカイブを作れるということを割と最近になってやっと知ったので、アドオンのXPIへのパッケージングまで全部アドオンでできるやん!と思ったんだけど、使い方がよく分からんかったので、実験と練習もかねてコンテキストメニュー拡張の短縮構文で使える汎用的な処理を書いてみた。

要点だけ抜き出すとこんな感じ。

// aSources = nsIFileの配列
// aZipFile = 作るZIPアーカイブのnsIFile
// aCompressionLevel = 圧縮率(0で無圧縮、9で最大圧縮、デフォルトは6)
zipFilesAs : function(aSources, aZipFile, aCompressionLevel) 
{
    // Firefox 3以降じゃないと動かないよ!
    if (!('nsIZipWriter' in Components.interfaces))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

    if (!aSources || !aSources.length || !aZipFile) return;

    if (aZipFile.exists() && !this.isZipFile(aZipFile))
        aZipFile.remove(true);

    const PR_RDWR        = 0x04;
    const PR_CREATE_FILE = 0x08;
    const PR_APPEND      = 0x10;
    const PR_TRUNCATE    = 0x20;

    var writer = Components
                    .classes['@mozilla.org/zipwriter;1']
                    .createInstance(Components.interfaces.nsIZipWriter);

    if (aCompressionLevel === void(0))
        aCompressionLevel = writer.COMPRESSION_DEFAULT;

    // 新規ZIPアーカイブと既存のZIPアーカイブではフラグが違う
    var flags = aZipFile.exists() ?
                    PR_RDWR | PR_APPEND :
                    PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE ;
    writer.open(aZipFile, flags);

    aSources.forEach(function(aFile) {
        var entry = this+aFile.leafName;
        // フォルダに対応するエントリの有無を調べる時は、
        // エントリ名の末尾に「/」が必要
        var entryForCheck = aFile.isDirectory() ? entry+'/' : entry ;

        // すでにあるエントリで、フォルダではない物は削除。
        // (フォルダを削除しないのは、内容をマージするため)
        if (!aFile.isDirectory() && writer.hasEntry(entryForCheck))
            writer.removeEntry(entry, false);

        // 新しいエントリを追加
        if (!writer.hasEntry(entryForCheck))
            writer.addEntryFile(entry, aCompressionLevel, aFile, false);

        if (aFile.isDirectory()) {
            var files = aFile.directoryEntries;
            while (files.hasMoreElements())
            {
                arguments.callee.call(
                    entry+'/',
                    files.getNext().QueryInterface(Components.interfaces.nsILocalFile)
                );
            }
        }
    }, '');

    writer.close();
},

// ファイルの内容を実際にZIPアーカイブとして読んでみて、ZIPアーカイブかどうか調べる
isZipFile : function(aFile)
{
    var isZip = false;

    if (!aFile || !aFile.exists()) return isZip;

    var reader = Components
                    .classes['@mozilla.org/libjar/zip-reader;1']
                    .createInstance(Components.interfaces.nsIZipReader);
    try {
        reader.open(aFile);
        try {
            var entries = reader.findEntries('*');
            while (entries.hasMore())
            {
                entries.getNext();
                isZip = true;
                break;
            }
            if (!isZip) {
                entries = reader.findEntries('*/');
                while (entries.hasMore())
                {
                    entries.getNext();
                    isZip = true;
                    break;
                }
            }
        }
        catch(e) {
        }
        reader.close();
    }
    catch(e) {
    }

    return isZip;
},
エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能