chrome URL からファイル一覧を取得する2011年01月12日 01時41分

Firefox にて、ディレクトリを指す chrome URL から、そのディレクトリ以下の全ファイルの URL を返すサンプルコードを書きました。

ChromeFiles.get("chrome://browser/content/");
/* => [ "chrome://browser/content/NetworkPanel.xhtml",
 *      "chrome://browser/content/aboutDialog.css",
 *      ...,
 *      "chrome://browser/content/browser.css",
 *      "chrome://browser/content/browser.js",
 *      "chrome://browser/content/browser.xul",
 *      ... ]
 */

特徴として、実際のファイルが (.jar または .xpi に) パッケージ化されているかどうかに関わらず、ファイル一覧を取得できることが挙げられます。ソースコード全体は上記 Gist へのリンクを参照してもらうとして、以下は各関数の解説です。

var ChromeFiles = {
    get: function CF_get(spec) {
        const ios = Cc['@mozilla.org/network/io-service;1'].
                    getService(Ci.nsIIOService);
        let uri = ios.newURI(spec, null, null);
        return this.getByURI(uri);
    },

    ...
};

単に、文字列として受け取った URL を nsIURI のインスタンスにして、getByURI() へ処理を委譲しているだけです。

getByURI: function CF_getByURI(uri) {
    // 1. ディレクトリを指す URL にする
    let baseURI = uri.clone().QueryInterface(Ci.nsIURL);
    baseURI.path = baseURI.directory;

    // 2. chrome URL からローカルファイルシステムでの URL へ変換
    const registry = Cc['@mozilla.org/chrome/chrome-registry;1'].
                     getService(Ci.nsIChromeRegistry);
    let localURI = registry.convertChromeURL(baseURI);

    // 3. ディレクトリ中のファイル名を取得
    let leafNames = null;
    if (localURI instanceof Ci.nsIFileURL) {
        leafNames = this.getLeafNamesByDirectory(localURI.file);
    } else if (localURI instanceof Ci.nsIJARURI) {
        leafNames = this.getLeafNamesByJARURI(localURI);
    } else {
        throw new Error('Unknown URI: ' + localURI.spec);
    }

    // 4. ディレクトリのパスとファイル名を結合
    let baseSpec = baseURI.spec;
    return leafNames.sort().map(function (leafName) baseSpec + leafName);
},
  1. chrome URL において、chrome://{package}/content/chrome://{package}/content/{package}.xul同じリソースを表しnsIIOService#newURI() の引数に前者を渡しても返ってくる URI オブジェクトの spec は後者になります。確実にディレクトリを指す URI オブジェクトを得るためには、自分で URI オブジェクトのプロパティを変更しなければなりません。

    ところが、newURI() で作られたオブジェクトは可変でない (immutable な) ことがあり、このときプロパティに値を設定しようとすると例外が発生します。chrome URL の場合 clone() で生成した URI オブジェクトは可変になるので、まずは URI オブジェクトを複製します。

    パスからディレクトリ部分だけを抜き出すのは、nsIURL インターフェースの directory プロパティを使うのが簡単です。nsIURL インターフェースを経由すれば、ディレクトリ以外にもファイル名や拡張子などをすぐに取得できます。

  2. chrome URL からローカルファイルシステム上でのファイル位置をあらわす URL への変換は、nsIChromeRegistry#convertChromeURL() で一発です。これにより得られる URL は大抵の場合 file URL か jar URL かのいずれかです。

  3. ディレクトリ中のファイル名一覧を配列として取得します。処理本体は file URL の場合と jar URL の場合で別になります。

    なお、QueryInterface() を使わなくとも、instanceof 演算子で nsIFileURL インターフェースを実装していることを確認できたなら、それ以降は nsIFileURLfile プロパティから nsIFile オブジェクトを取得できます。

  4. 得られたファイル名一覧の順序はわからないので、辞書順で並べ替えます。その後にディレクトリ部分を表す chrome URL と結合すれば、ディレクトリ直下のファイルを指す chrome URL の一覧が得られます。

getLeafNamesByDirectory: function CF_getLeafNamesByDirectory(dir) {
    let files = dir.directoryEntries;
    let leafNames = [];
    while (files.hasMoreElements()) {
        let file = files.getNext().QueryInterface(Ci.nsIFile);
        if (file.isFile())
            leafNames.push(file.leafName);
    }
    return leafNames;
},

ディレクトリを表す nsIFile オブジェクトから、その子ファイルの名前一覧を取得します。単に子ファイルを列挙していき、それがディレクトリなどでないときにファイル名を取得するだけです。

getLeafNamesByJARURI: function CF_getLeafNamesByJARURI(jarURI) {
    // 1. ZipReader を作成
    let zip = this.openZipReader(jarURI.JARFile);

    try {
        // 2. ディレクトリ直下のファイルのパスを取得
        let baseEntry = jarURI.JAREntry;
        let pattern = baseEntry + '?*~' + baseEntry + '?*/*';
        let entries = zip.findEntries(pattern);

        // 3. ファイル名部分だけを抜き出し、返す
        let leafNames = [];
        while (entries.hasMore())
            leafNames.push(entries.getNext().substring(baseEntry.length));
        return leafNames;
    } finally {
        zip.close();
    }
},

jar URI は jar:file://path/to/file.jar!/path/to/entry/ のような形で表されます。nsIJARURI インターフェースは、file://path/to/file.jar の部分を示す JARFile プロパティ (返ってくるのは nsIURI オブジェクト) と、/path/to/entry/ の部分を指す JAREntry プロパティ (返ってくるのは文字列) を持っています。

  1. JAR ファイル (または XPI ファイル) は ZIP 書庫なので、内部のファイル情報を読み取るためには nsIZipReader のインスタンスを作成し書庫を開く必要があります。

  2. 書庫内部のファイル名一覧を取得するには findEntries() を使います。ここで指定するファイル名のパターンにおいて、"?" は任意の1文字を、"*" は任意の文字列を、"pattern1~pattern2"pattern1 にマッチするが pattern2 にはマッチしないものを表します。

    "/path/to/entry/*" というパターンでは /path/to/entry/ 自信も含まれてしまうので、ディレクトリではないファイルだけを抽出するために "/path/to/entry/?*" と指定します。また、それだけだと子孫ディレクトリ中のファイルも含まれるので、"/path/to/entry/?*/*" を除外してやります。

  3. findEntries() で得られた値にはディレクトリ部分も含まれるので、その部分は切り取ってファイル名だけにします。

openZipReader: function CF_openZipReader(uri) {
    let zip = Cc['@mozilla.org/libjar/zip-reader;1'].
              createInstance(Ci.nsIZipReader);
    if (uri instanceof Ci.nsIFileURL) {
        // 1. file URL なら単にそのファイルを開く
        zip.open(uri.file);
    } else if (uri instanceof Ci.nsIJARURI) {
        // 2. jar URL なら JAR ファイル内部のファイルを開く
        let innerZip = this.openZipReader(uri.JARFile);
        zip.openInner(innerZip, uri.JAREntry);
    } else {
        throw new Error('Unknown URI: ' + uri.spec);
    }
    return zip;
},
  1. JAR ファイルがローカルファイルシステム上に直接存在するなら、単に open() メソッドにファイルオブジェクトを渡して開くだけです。

  2. 開こうとする JAR ファイルが別の書庫内に存在することもあります。その場合は openInner() に、JAR ファイルが含まれる書庫と、その書庫内での JAR ファイルのパスを指定してやります。

    nsIZipReader#openInner() は Firfox 4 で追加されたものですが、Firefox 4 より前では jar URL がネストすることはないといっていいので、ここで使っても問題ないでしょう。

たとえば Firefox 4 Beta でツリー型タブを使うと、chrome://treestyletab/content/ の実体は jar:jar:file://{profile}/extensions/treestyletab@piro.sakura.ne.jp.xpi!/chrome/treestyletab.jar!/content/treestyletab/ といった URL になりますが、上記のようにすればその内部のファイル構成を知ることができます。

また、resource://gre/modules/XPCOMUtils.jsm といった resource URL に関しても、nsIResProtocolHandler#resolveURI() を使えばローカルファイルシステム上の URL へ変換でき、上と同様にファイル一覧の取得などが可能になります。

コメント

コメントをどうぞ

※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。

※投稿には管理者が設定した質問に答える必要があります。

名前:
メールアドレス:
URL:
次の質問に答えてください:
「ハイパーテキストマークアップ言語」をアルファベット4文字でいうと?

コメント:

トラックバック

このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2011/01/12/5634277/tb