はじめてのMorphicチュートリアル(番外編)「ScratchCatをダウンロードする」

Cat_1_Bitmap

Scratchのキャラクターの猫をサイトからダウンロードする方法について。

Scratch Mediaのウェブページを開いて、そこから「SVG+PNG (2 Costumes)」という圧縮ファイルをダウンロードして展開するということなので、ブラウザとOS上の操作があれば解決する簡単な仕事なのだが、これをPharoでやるとどうなるかということである。

まず、http://wiki.scratch.mit.edu/w/images/ScratchCat.zip のファイルをダウンロードするには、以下のようにZnEasyクラスを使うのが簡単である。

res := ZnEasy get: 'http://wiki.scratch.mit.edu/w/images/ScratchCat.zip'.

こうすると、resにはZnResponseオブジェクトが返ってくる。問題なければ以下のような内容となる。

"a ZnResponse(200 OK application/zip 12017B)"

12017バイトのZIPファイルが得られることがわかる。

ZIPファイルの内容を得るには、contentsというメッセージを送れば良い。

res contents.

ZIPファイルをアーカイブとして読み込むには、ZipArchiveのインスタンスに対してreadFrom:でストリームを与える。

arc := ZipArchive new readFrom: res contents readStream.

アーカイブされたファイル群(メンバー)は以下のように知ることができる。

arc members.
"an OrderedCollection(a ZipFileMember(Cat_1.svg) a ZipFileMember(Cat_2.svg)
 a ZipFileMember(Cat_1_Bitmap.png) a ZipFileMember(Cat_2_Bitmap.png))"

4つの画像ファイルが入っていることがわかる。

アーカイブから特定の画像ファイルを保存するには、以下のようにすればよい。

arc extractMember: 'Cat_1_Bitmap.png'.

簡単!と思ったのだが落とし穴があった。上をDo itするとエラーが発生する。
error

どうやらZipFileMemberクラスの extractToFileNamed:inDirectory: メソッドに問題があるらしい。
エラー内容

エラーを起こしている箇所はここである。

    fullDir 
        forceNewFileNamed: file basename 
        do: [:stream |  self extractTo: stream]]

よく見るとメソッド内でfullDirの値を設定している箇所がない。実際、fullDirの内容はnilだし、エラーはUndefinedObject(つまりnil)に対するメッセージ送信となっている。

これは想像だが、以前にFileDirectoryをFileSystemに置き換えた際、この部分の修正が取り残されたのではないだろうか。コメントぐらい残しとけよって感じである。Pharo1.4までさかのぼってみると、想像どおりFileDirectoryを用いてfullDirが作られていた。

extractToFileNamed: aLocalFileName inDirectory: dir
    | fullName fullDir |
    self isEncrypted ifTrue: [ ^self error: 'encryption unsupported' ].
    fullName := dir fullNameFor: aLocalFileName.
    fullDir := FileDirectory forFileName: fullName.
    fullDir assureExistence.
    self isDirectory 
        ifFalse: [ fullDir 
            forceNewFileNamed: (FileDirectory localNameFor: fullName)
            do: [:stream |  self extractTo: stream]]
    ifTrue: [ fullDir assureExistence ]

こういうことでメゲていてはいけない。せっかく全てのソースがアクセス可能なのだから自分で何とかすることを考えるべきである。

fullDirにはforceNewFileNamed:do:というメッセージとassureExistenceというメッセージが送られている。前者は新しいファイルを作ってファイルの中身を流し込んでおり、後者はディレクトリを作っているにすぎない。これを現在のFileSystemで実現することを考える。

    self isDirectory
        ifFalse: [ 
            file isFile
                ifTrue: [ file delete ].
            self extractTo: file writeStream ]
        ifTrue: [ file ensureCreateDirectory ]

上記が変更部分である。self isDirectory以降を書き換える。

これで晴れてZIPアーカイブからファイルを展開することができる。

arc extractMember: 'Cat_1_Bitmap.png'.
arc extractMember: 'Cat_2_Bitmap.png'.

以下をDo itすれば、展開したファイルをモーフとして取り込むことができる。

(PNGReadWriter formFromFileNamed: 'Cat_1_Bitmap.png') asMorph openInWorld.
(PNGReadWriter formFromFileNamed: 'Cat_2_Bitmap.png') asMorph openInWorld.

(番外編おわり)