CMSと静的ファイル管理に関する考察の(更に)続き。
公開日 : 2007-04-30 11:54:38
結局のところ、Movable TypeのCMSは現状「コンテンツマネジメントシステム」ではあっても「ファイルマネジメントシステム」ではないということなんだろう。
アップロードしたファイルをMTから削除したりすることができないというのも典型的な例か(TypePadベースのココログとかでは可能だし(インターフェイスは最悪だけど)、今後のバージョンでは実装されるのではないだろうか)。
で、前回・前々回の続きである。
結局どうプログラミングしてどう実装するというよりも、どう設計するか。
現状は産んだら産みっぱなし...というかファイルを生成したら生成しっぱなしなので、書き出したファイルの情報を保持しておくのが楽だろう。アーカイブマッピングやファイル名、カテゴリが変わるごとに旧パス情報と新パス情報を比較して何らかの処理をするという方法もあるにはあるだろうが色んな意味でコストが高くつきそうだし。
ということで以下のようにしてみた。
package MT::Content;
use strict;
use MT::Object;
@MT::Content::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
columns => [ 'id', 'file', 'content',
'modified', 'size', 'blog_id',
'digest', 'status',
],
indexes => {
id => 1,
file => 1,
modified => 1,
blog_id => 1,
status => 1,
},
datasource => 'content',
});
MT::Content(MT::Objectのサブクラス)
フィールド名 | タイプ | 説明 |
---|---|---|
id | integer (primary key,auto_incriment) | 一意なID |
file | text | ファイルのパス |
content | text | 書き出される(べき)HTML |
modified(※1) | integer | 書き出し時刻(Unixtime) |
size | integer | ファイルサイズ |
blog_id | integer | ブログのID |
digest | text | textの要約(digest) |
status | integer | ステータス |
利用方法は様々。一例として、前回の例であげたような「疑似的」ダイナミックパブリッシング等で使える。再構築の際にファイルシステムにファイルを書き出すのとデータベースに保存する違いだけで、構築されるデータは(プラグインの処理も含めて)まったく同様になる(一部BuildPage, BuildFileコールバックの際の処理は反映されないが、これはプラグインを書き換えるかコールバックも含めて上書きしてやることで(=プラグインの中からさらにコールバックを呼び出すことが可能なら)対応できるだろう)。
環境にかなり依存すると思うが、実際にテストしてみた環境ではファイルに書き出す約半分強の時間で全再構築が完了した。
あとは、ダイナミックパブリッシングの閲覧プログラム側でConditional GET([HTTP_IF_MODIFIED_SINCE]によって304を返してブラウザキャッシュを使わせる)に対応させたり、何ならリクエストが発生した段階でファイルを書き出すような処理も簡単だろう。
ダイナミックパブリッシングを利用しないのであれば content フィールドにHTMLを丸ごと入れる必要はなく、digest(※2) と size のみを比較して更新されたかどうかをチェックすれば良い。
my $digest = &md5_hex($html);
my $len = length($html);
use MT::Content;
my $content = MT::Content->load({ file => $file });
my $if_mod = 1;
unless (defined $content) {
$content = MT::Content->new;
} else {
if (($content->size == $len) && ($content->digest eq $digest)) {
$if_mod = 0;
}
}
if ($if_mod) {
# 更新されていたらDBを更新
}
CMSから吐き出されたファイルのパス/書き出されたファイルの時間を保持しているので、例えば「前回サイトを更新した以後に更新されているファイルの一覧」をリストアップすることも簡単(※3)。
また、status フィールドをうまく使えば、
- 全レコードの status を 一旦 0 にする
- 全再構築して status を 1 にする
- status が 0 のファイルが存在していたら(ゴミ)ファイルとレコードを削除する
というようなしくみも作れる。(※4)
- ※1) modified_on にしてはいけない。というか modified_on にするのであれば timestamp 形式にしないといけない...みたいだ。
- ※2) MT::Utilの perl_sha1_digest_hex を使ってdigestを生成したところ、再構築の時間が2倍くらいかかったのでDigest::MD5(md5_hex)を利用した。
- ※3) 以前にも必要があって作ったことがあって、「CMSはコンテンツ管理のみに利用, 納品は出来上がったファイルのみ, 更新時には差分ファイルだけ納品」というケースがWeb制作の現場では良くある。
以前作った時はまだMTのしくみがよく分かっていなかったのでPHPからMySQLにアクセスしたりファイルのタイムスタンプをチェックしたりという感じでかなり力技の実装だった。 - ※4) というか既に殆ど出来た。
nph-mtview.cgi(それなりにintelligence ? なHTTPサーバ)
#!/usr/bin/perl -w
use strict;
use lib qw (/Applications/MAMP/htdocs/mt/lib/);
use lib qw (/Applications/MAMP/htdocs/mt/extlib/);
my $file = '/Applications/MAMP/htdocs';
my $index = 'index.html';
use MT;
use MT::Content;
use HTTP::Date;
my $CRLF = "\r\n";
my $mt = MT->new(Config => '/Applications/MAMP/htdocs/mt/mt-config.cgi');
$file .= $ENV{"REDIRECT_URL"};
if ($file =~ /\/$/) {
$file .= $index;
}
my $content = MT::Content->load({ file => $file });
if (defined $content) {
my $length = $content->size;
my $http_if_mod = $ENV{'HTTP_IF_MODIFIED_SINCE'};
my $http_if_none = $ENV{'HTTP_IF_NONE_MATCH'};
my $modified = &time2str($content->modified);
my $etag = $content->digest;
if (($http_if_mod && ($http_if_mod < $modified)) ||
($http_if_none && ($http_if_none != $etag)) ||
! $http_if_mod ) {
print "HTTP/1.1 200 OK$CRLF";
print "Content-length: $length$CRLF";
print "Last-modified: $modified$CRLF";
print "Etag: $etag$CRLF";
print "Content-Type: text/html$CRLF$CRLF";
print $content->content;
} else {
print "HTTP/1.1 304 Not Modified$CRLF";
print "Content-length: $length$CRLF";
print "Last-modified: $modified$CRLF$CRLF";
}
} else {
print "HTTP/1.1 404 File Not Found$CRLF";
print "Content-Type: text/html$CRLF$CRLF";
print "404";
}
結局のところ、誰が、どこでコストを負担するかの話なんだよな。色んな意味で。
まだ続く...(Movable Type用ファイルマネージャーの設計と実装(1))