hako-mente.cgiの解説

hako-mente.cgiは

1)新しい箱庭のデータセットを作る
2)箱庭データの最終更新日付を変更する
3)バックアップのデータを現役に持ってくる

などの機能があります。

重要な部分は太字で、ソース内のコメントは緑色で、私の注釈は赤字で書き込みました。
また、分かりやすいようにインデント(段付け)してルーチンの区切りを分かりやすくしています。

ちなみに、このページをコピー・ペーストしてCGIファイルにしても動きませんので悪しからず……。

戻る

#!/usr/local/bin/perl
# ↑はサーバーに合わせて変更して下さい。
(ちなみに、FreeBSDでは/usr/bin/perlでした。)


#----------------------------------------------------------------------
# 箱庭諸島 ver2.30
# メンテナンスツール(ver1.01)
# 使用条件、使用方法等は、hako-readme.txtファイルを参照
#
# 箱庭諸島のページ: http://www.bekkoame.ne.jp/~tokuoka/hakoniwa.html
#----------------------------------------------------------------------



# ――――――――――――――――――――――――――――――
# 各種設定値
# ――――――――――――――――――――――――――――――

# マスターパスワード

my($masterpassword) = 'yourpassword';
(hako-main.cgiと同じにするのが望ましい。違っていても大丈夫だけど……。)

# 1ターンが何秒か
my($unitTime) = 21600; # 6時間
(これは必ずhako-main.cgiと同じにして下さい。)

# ディレクトリのパーミッション
my($dirMode) = 0755;
(プロバイダによっては0705、0700……など、わけのわからん数字を求められる事があります。でも要は「CGIによって書き込み可能」「CGIによって読み出し可能」になっていれば問題ありません。)

# このファイル(のURL)
my($thisFile) = 'http://場所/hako-mente.cgi';
(ブラウザで見た時のこのスクリプトへのURLです。hako-mente.cgiを別の名前で設置する時は忘れずにここのアドレスも変えてください)

# データディレクトリの名前
# hakojima.cgi中のものと合わせてください。
(hakojima.cgiはhako-main.cgiの間違いです。)
my($dirName) = 'data';
(ただし、hako-mente.cgiとhako-main.cgiを違うディレクトリに置く場合、「hako-mente.cgiから見たデータディレクトリ」を書いて下さい。

例えば、
    cgi-bin/
      |
      +−hako-main.cgi
      +−/data
      +−/data1
      +−/data2
       :
      +−/mentenance
            |
            +−hako-mente.cgi
なんて構成にしたあった場合、
(セキュリティーの関係でこうしたい人もいるでしょ?)

hako-main.cgiでの$dirNameは”data”ですが、
hako-mente.cgiでの$dirNameは”../data”になります。
ようは、それぞれのCGIから見て同じディレクトリを指すようにせっていすればいいんです。

これが2つのCGIで異なると、「menteでデータを作ったのにゲームの方からはデータディレクトリがないと言われる!」というトラブルの原因になります。)



# use Time::Localが使えない環境では、'use Time::Local'の行を消して下さい。
# ただし、更新時間の変更が'秒指定で変更'しかできなくなります。

use Time::Local;
(@NIFTYで設置する場合はコメントアウトしてください。
 もし、削除するべきか分からない時は、

 #usr/local/bin/perl
 
 use Time::Local
 
 print 'booooooo!\n';

みたいな簡単なスクリプトを設置して確かめて見ましょう。
Internal Server Errorになるサーバでは Time::Localが使えません。)

# ――――――――――――――――――――――――――――――
# 設定項目は以上
# ――――――――――――――――――――――――――――――

# 各種変数

my($mainMode);
→CGIが呼ばれた理由
  "delete"  …… バックアップ、又は現行のデータの削除
  "current" …… 選択されたバックアップを現行データにする
  "time"   …… 最終ターン更新時間を変更する(日付指定)
  "stime"  …… 最終ターン更新時間を変更する(秒指定)
  "new"    …… 新規データ作成
  その他   …… 現状の表示だけを行う

 それぞれの場合で使われるパラメータはそれぞれのルーチンを参考

my($inputPass);
→入力されたパスワード(平文)

my($deleteID);
→削除モードのときの削除するバックアップデータの番号(0は現行データ)

my($currentID);
→現行データにするバックアップの番号

my($ctYear);
my($ctMon);
my($ctDate);
my($ctHour);
my($ctMin);
my($ctSec);
→新しい最終ターン更新時間(日付指定モード)
 (秒指定の時は$ctSecだけが使用される)

<<<<<<実際のメインルーチンの始まり>>>>>>

HTMLヘッダーの表示(ページのタイトルを変更したい時はここを変える!)

print <<END;
Content-type: text/html

<HTML>
<HEAD>
<TITLE>箱島2 メンテナンスツール</TITLE>←ページタイトル
</HEAD>
<BODY>

END

cgiInput();     ← Formから、入力されたデータを受け取る

前の画面で入力された値によってモード分岐する
if($mainMode eq 'delete') {
    if(passCheck()) {
        deleteMode();    データ削除モード
    }
} elsif($mainMode eq 'current') {
    if(passCheck()) {
        currentMode();   バックアップデータを現行データとする
    }
} elsif($mainMode eq 'time') {
    if(passCheck()) {
        timeMode();     最終ターン更新時間を変える(日付指定)
    }
} elsif($mainMode eq 'stime') {
    if(passCheck()) {
        stimeMode();     最終ターン更新時間を変える(1970年からの秒指定)
    }
} elsif($mainMode eq 'new') {
    if(passCheck()) {
        newMode();     新規データ作成
    }
}

mainMode();     メイン表示モード(これは全てのモードからも呼ばれます)

print <<END;   フッターの表示(最後に管理人などへのリンクをつけるときはここへ!
</FORM>
</BODY>
</HTML>

END

実質的にメインループはここで終わり



ここからはサブルーチン群


sub myrmtree {  指定されたディレクトリをファイルもろとも削除するルーチン
    my($dn) = @_;     引数は”削除するディレクトリのパス(最後は「/」なし)”
    opendir(DIN, "$dn/");
    my($fileName);
    while($fileName = readdir(DIN)) {   単純に与えられたディレクトリの
                           ファイルを1つずつ消して、

        unlink("$dn/$fileName");         
    } 

    closedir(DIN);
    rmdir($dn);     最後にそのフォルダを消します。
}           ※UNIXではディレクトリにファイルが入っている時は、そのディレクトリを
             消す事ができないのです。

sub currentMode { バックアップのデータを現役にするルーチン
    myrmtree "${dirName}";  現役のデータを削除
    mkdir("${dirName}", $dirMode); 現役データのフォルダを作る
    opendir(DIN, "${dirName}.bak$currentID/");
    my($fileName);
    while($fileName = readdir(DIN)) { バックアップの中のファイルを現役にコピー
        fileCopy("${dirName}.bak$currentID/$fileName", "${dirName}/$fileName");
    
    closedir(DIN);
}

sub deleteMode { 指定されたデータを消すルーチン
    if($deleteID eq '') {
        myrmtree "${dirName}"; 現役データの場合
    } else {
        myrmtree "${dirName}.bak$deleteID"; バックアップの場合
                                ($deleteID=バックアップ番号)
    }
    unlink "hakojimalockflock"; ロックファイルがあった場合は消します
                     (ファイルロックの場合たまに残ってる事がある)
}

sub newMode {  新しいデータファイルを作るルーチン
             このルーチンは現役データもバックアップもないときしか呼べません

    mkdir($dirName, $dirMode); 新規データディレクトリを作製

    # 現在の時間を取得
    my($now) = time;
    $now = $now - ($now % ($unitTime));  すぐに更新がかかるようにする

    open(OUT, ">$dirName/hakojima.dat"); # ファイルを開く
                            
大元のデータだけ作っています
    print OUT "1\n"; # ターン数1
    print OUT "$now\n"; # 開始時間    1970年1月1日00:00からの秒数で入ってます
    print OUT "0\n"; # 島の数
    print OUT "1\n"; # 次に割り当てるID

    # ファイルを閉じる
    close(OUT);
}

sub timeMode { 最終ターン行進時間変更(日付形式で)
    
    $ctMon--;   (UNIXの中では、月は0月〜11月なんだよ。知ってた?)
    $ctYear -= 1900;
    $ctSec = timelocal($ctSec, $ctMin, $ctHour, $ctDate, $ctMon, $ctYear);
  ここのtimelocal関数は「年月日時分秒を1970年からの秒数に変換する関数」なんだけど
  これは「use time::local」してないと使えない。
  time::localの追加モジュールがサーバのperlにインストールされていないと使えない。
  (これがNIFTYでuse time::localを削除しなくてはならない理由です。)

    stimeMode(); 後は秒数指定と同じルーチンを使っています。
}

sub stimeMode {  最終ターン更新時間変更(秒形式)
    my($t) = $ctSec;
    open(IN, "${dirName}/hakojima.dat"); 大元データファイル読み込み
    my(@lines);
    @lines = <IN>;  大元のデータファイルをlines配列に入れてます。まるごと。
    close(IN);

    $lines[1] = "$t\n";   行数は0行目から始まります。「1」行目は最終更新時間です。

    open(OUT, ">${dirName}/hakojima.dat");
    print OUT @lines; 大元のデータファイルを書き戻しています。
    close(OUT);
}

sub mainMode { 全体を表示します。ボタンとか全体的なデザインはここで!

    opendir(DIN, "./");  スクリプトの親ディレクトリの中にあるディレクトリを探します。

    print <<END;   本文のタイトルです。
        <FORM action="$thisFile" method="POST">
        <H1>箱島2 メンテナンスツール</H1>
        <B>パスワード:</B><INPUT TYPE=password SIZE=32 MAXLENGTH=32         NAME=PASSWORD></TD>

    END

    # 現役データ
    if(-d "${dirName}") { 現役データ(のディレクトリ)があるか?
        dataPrint("");  現役データがあるときは現役データを表示する
    } else {
        print <<END;
            <HR>       現役データがないときは「新しいデータを作る」の
                      ボタンを表示

            <INPUT TYPE="submit" VALUE="新しいデータを作る" NAME="NEW">

        END
    }

    # バックアップデータの一覧表示
    my($dn);
    while($dn = readdir(DIN)) {  スクリプトと同じディレクトリの中を検索
                       [現役データディレクトリ名].bak[ナンバー]です。
        if($dn =~ /^${dirName}.bak(.*)/) { ちなみに現役データのディレクトリは
                             マッチしません
            dataPrint($1);  $1にはifの行でマッチしたディレクトリ名の最後1字
        }
    } 
    closedir(DIN);
}

間違えやすいけど、↑ここまでがmainModeルーチンの中です。


# 表示モード 現役データかバックアップデータの日付と削除ボタン、現役ボタンの表示
sub dataPrint {

    my($suf) = @_;  $suf=呼び出し元で渡された「ディレクトリ名」の最後の文字

    print "<HR>";   水平線(HTML)で区切る

    if($suf eq "") {   
        open(IN, "${dirName}/hakojima.dat");  $sufが=""だったら現役データ
        print "<H1>現役データ</H1>";
    } else {
        open(IN, "${dirName}.bak$suf/hakojima.dat");  $suf <>""ならバックアップ
                                     データ

        print "<H1>バックアップ$suf</H1>";
    }

    my($lastTurn);
    $lastTurn = <IN>;  大元データからターン数を読み込む
    my($lastTime);
    $lastTime = <IN>;  大元データから最終更新時間を読み込む

    my($timeString) = timeToString($lastTime); 時間を表示用に整形する

    print <<END;
        <B>ターン$lastTurn</B><BR>
        <B>最終更新時間</B>:$timeString<BR>
        <B>最終更新時間(秒数表示)</B>:1970年1月1日から$lastTime 秒<BR>
        <INPUT TYPE="submit" VALUE="このデータを削除" NAME="DELETE$suf">

    END

    if($suf eq "") {  現役データだった場合
        my($sec, $min, $hour, $date, $mon, $year, $day, $yday, $dummy) =
        localtime($lastTime);
        $mon++;
        $year += 1900;

        print <<END; 最終更新時間変更用の入力BOXを書く
            <H2>最終更新時間の変更</H2>
            <INPUT TYPE="text" SIZE=4 NAME="YEAR" VALUE="$year">年
            <INPUT TYPE="text" SIZE=2 NAME="MON" VALUE="$mon">月
            <INPUT TYPE="text" SIZE=2 NAME="DATE" VALUE="$date">日
            <INPUT TYPE="text" SIZE=2 NAME="HOUR" VALUE="$hour">時
            <INPUT TYPE="text" SIZE=2 NAME="MIN" VALUE="$min">分
            <INPUT TYPE="text" SIZE=2 NAME="NSEC" VALUE="$sec">秒
            <INPUT TYPE="submit" VALUE="変更" NAME="NTIME"><BR>
            1970年1月1日から<INPUT TYPE="text" SIZE=32 NAME="SSEC"
             VALUE="$lastTime">秒
            <INPUT TYPE="submit" VALUE="秒指定で変更" NAME="STIME">

        END
    } else {
        print <<END; バックアップの場合は「現役にする」ボタンを表示
            <INPUT TYPE="submit" VALUE="このデータを現役に"
             NAME="CURRENT$suf">
        END
    }
}

sub timeToString {  日付を表示用に整形する
    my($sec, $min, $hour, $date, $mon, $year, $day, $yday, $dummy) =
    localtime($_[0]);  $_[0]は引数の最初の要素です
    $mon++;
    $year += 1900;

    return "${year}年 ${mon}月 ${date}日 ${hour}時 ${min}分 ${sec}秒";
}

# CGIの読みこみ フォームで入力された数値や、押されたボタンを調べる
sub cgiInput {
    my($line);

    # 入力を受け取る
    $line = <>;       ここの3行はCGIから文字列を取り込むときのお約束です
    $line =~ tr/+/ /;
    $line =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;

    if($line =~ /DELETE([0-9]*)/) {  削除モードのとき
        $mainMode = 'delete';    
        $deleteID = $1;   削除するデータの番号(0=現役)
    } elsif($line =~ /CURRENT([0-9]*)/) { バックアップを現役にするモード
        $mainMode = 'current';
        $currentID = $1;  現役にするバックアップデータの番号(1〜)
    } elsif($line =~ /NEW/) {   現役データを新規作成するモード
        $mainMode = 'new';
    } elsif($line =~ /NTIME/) {  日付形式で最終ターン更新時間を変更する
        $mainMode = 'time';
        if($line =~ /YEAR=([0-9]*)/) { 年月日時分秒を取り込む
            $ctYear = $1; 
        }
        if($line =~ /MON=([0-9]*)/) {
            $ctMon = $1; 
        }
        if($line =~ /DATE=([0-9]*)/) {
            $ctDate = $1; 
        }
        if($line =~ /HOUR=([0-9]*)/) {
            $ctHour = $1; 
        }
        if($line =~ /MIN=([0-9]*)/) {
            $ctMin = $1; 
        }
        if($line =~ /NSEC=([0-9]*)/) {
            $ctSec = $1; 
        }
    } elsif($line =~ /STIME/) {  秒単位で最終ターン更新時間を変更するモード
        $mainMode = 'stime';
        if($line =~ /SSEC=([0-9]*)/) {
            $ctSec = $1; 
        }
    }

    if($line =~ /PASSWORD=([^\&]*)\&/) { パスワードを取り込む(全モード)
        $inputPass = $1;
    }
}

# ファイルのコピー フォルダー内の全てのファイルをコピーする
sub fileCopy {
    my($src, $dist) = @_;  最初の引数がコピー元、次の引数がコピー先
    open(IN, $src);
    open(OUT, ">$dist");  1ファイルずつ開いて新しいファイルに書き込み(ぉ
    while(<IN>) {
        print OUT;
    }
    close(IN);
    close(OUT);
}

# パスワードチェック
sub passCheck { 入力されたパスワードとセットされているマスターパスワードを比較
    if($inputPass eq $masterpassword) {
        return 1;  OK
    } else {
        print <<END;
            <FONT SIZE=7>パスワードが違います。</FONT>
        END
        return 0;  NG
    }
}

1;  ←どうやら礼儀正しいUNIXのプログラムでは最後はOK(1)を返すのが正しいらしい

 

でも、hako-mente.cgiを改造しようなんて人はほとんどいないと思う(爆)

戻る