###
### Yet Anather Dice Chat: Library
### $Id: yadchatlib.pl,v 1.6 2007/06/07 12:20:57 jyugoya Exp $
### YADChat / 2007 &copy; 結城由羅＠世界忍者国 / BSD Lisence
###

### 保留中変数 ###
$rep_color = "000000";

#####  関数：エラー表示 (共通) #####
sub error {
    local($errmsg) = $_[0];
    local($lockfile) = $CONF{'lockdir'}.'/'.$IN{'room'}.'.lock';
    local($memlock) = $CONF{'lockdir'}.'/'.$IN{'room'}.'.memlock';
    local($rplock) = $CONF{'lockdir'}.'/'.$IN{'room'}.'.rplock';

    # ロックファイルの解除
    if ($CONF{'lockmode'}) {
        &unlock($lockfile);
        &unlock($memlock);
        &unlock($rplock);
    }

    &printHeader();
    print << "EOM";
<body>
<center><h3>ERROR !</h3>
<font color='red'>$errmsg</font></center>
</body></html>
EOM
    exit(0);
}

##### 関数：エラー表示 (&error()と重複) #####
sub printError {
    &printHeader();

    print $query->h1($CONF{'title'});
    print $query->p("ERORR: " . $_[0]);

    if ($DEBUG) {
        foreach $key (keys %IN) {
            print $query->p($key . ": " . $IN{$key});
        }
    }
    print $query->p("<a href=\"$CONF{'indexcgi'}\" target=\"_top\">[ 一覧へ戻る ]</a>");

    # 表示終了
    print $query->end_html;
    exit(0);
}

##### 関数：デバッグ表示 #####
sub printDebug {
    print $query->h2("デバッグ情報");
    print "<div class=\"main\">";
    print $query->p($errmsg);
    print "Input: ";
    foreach $key (keys %IN) {
        print $key . " = " . $IN{$key} . ", ";
    }
    print "<br/>Config: ";
    foreach $key (keys %CONF) {
        print $key . " = " . $CONF{$key} . ", ";
    }
    print "</div>";
}

##### 関数：注意事項表示 #####
sub printNotice {
    print $query->h2("注意事項");
    print<<EOM;
<ul class="sub">
<li>チャットルーム作成プログラムです。</li>
<li>チャットルームの作成は誰でもできます。</li>
<li>チャットルームの終了は作成者と管理者しかできません。</li>
<li>チャットルームの削除は管理者しかできません。</li>
<li>たくさんのチャットルームを同時利用すると負荷がかかりますので良識の範囲でご利用ください。</li>
<li>mihanaさんのプログラムをモデルにスクラッチし、BSDライセンス化してます。</li>
<li>チャットルームは豪華絢爛チャット（comchat互換品）を変更したものです。長文モードはありません。ダイスは2d6のように入力すると置き換わります。</li>
</ul>
EOM
}

##### 関数：部屋一覧表示 #####
sub printRoomList {
    print<<HEADER;
<H2>チャットルーム一覧</H2>

<table class="sub">
<tr>
<th align="center">ルーム名</TD>
<th align="center">各種閲覧</TD>
<th align="center">コメント</TD>
</tr>
HEADER

    # 一覧表示
    local(@index) = &readIndex();
    foreach $room (@index){
        local($idx, $name, $comment, $state, $pass) = split(/<>/, $room, 5);
        if ($state){
            local($loglink) = &genLogLinkString($idx);
            print <<ROW;
<tr>
<td align="left"><a href="$CONF{'chatcgi'}?room=$idx">$idx $name</a></td>
<td align="left">
[<a href="$CONF{'chatcgi'}?room=$idx&weight=light">携帯入口</a>]
$loglink
</td><td align="left">$comment</td>
</tr>
ROW
        }
    }

    print $query->end_table;

    # メニュー表示
    print<<MENU;
<p class="sub">
<a href="$CONF{'indexcgi'}?mode=make">新規チャットルーム作成</a>
 | <a href="$CONF{'indexcgi'}?mode=log">ログファイル一覧</a>
 | <a href="$CONF{'indexcgi'}?mode=admin">管理メニュー</a>
</p>
MENU
}

##### 関数：部屋作成画面表示 #####
sub printMakeRoom {
    print $query->h2("チャットルーム作成");
    print<<EOM;
<div class="main">
<form action="$CONF{'indexcgi'}" method="post" class="main">
<table>
<tr><td>ルーム名</td><td><input type="text" name="name" size="40"></td></tr>
<tr><td>コメント</td><td><input type="text" name="comment" size="40"></td></tr>
<tr><td>パスワード</td><td><input type="password" name="pass" size="8"></td></tr>
<tr><td></td><td><input type="submit" name="make" value="チャットルームを作成する"></td></tr>
</table>
</form>
</div>
EOM
}

##### 関数：ログファイル一覧表示 #####
sub printLogList {
    print $query->h2("ログファイル一覧");
    print<<HEAD;
<table class="sub">
<tr>
<th align="center">ルーム名</TD>
<th align="center">コメント</TD>
</TR>
HEAD
    # 一覧表示
    local(@index) = &readIndex();
    shift(@index);
    foreach $room (@index){
        local($idx, $name, $comment, $state, $pass) = split(/<>/, $room, 5);
        if (!$state){
            print<<ROW;
<tr>
<td align="left"><a href="$CONF{'logdir'}/$idx.html">$idx $name</a></td>
<td align="left">$comment</td>
</tr>
ROW
        }
    }
    print $query->end_table;
}

##### 関数：管理メニュー表示 #####
sub printAdminMenu {
    &printClose();
    &printDelete();
}

##### 関数：終了メニュー表示 #####
sub printClose {
    print $query->h2("チャットルームの終了");
    print<<HEAD;
<div class="main">
<form action="$CONF{'indexcgi'}" method="post" class="main">
<table>
<tr>
<td>チャットルーム選択</td>
<td><select name="close">
HEAD
    # リスト表示
    local(@index) = &readIndex();
    shift(@index);
    foreach $room (@index){
        local($idx, $name, $comment, $state, $pass) = split(/<>/, $room, 5);
        if ($state){
            print <<OPTION;
<option value="$idx">$idx $name</option>
OPTION
        }
    }
    print <<FOOT;
</select></td>
</tr>
<tr>
<td>パスワード</td>
<td><input type="password" name="pass" size="15"></td>
</tr>
<tr><td></td><td><input type="submit" name="admin" value="終了"></td></tr>
</table>
</form>
</div>
FOOT
}

##### 関数：削除メニュー表示 #####
sub printDelete {
    print $query->h2("チャットルームの削除");
    print <<HEAD;
<div class="main">
<form action="$CONF{'indexcgi'}" method="post" class="main">
<table>
<tr>
<td>ゲーム選択</td>
<td><select name="del">
HEAD

    # 一覧作成
    local(@index) = &readIndex();
    shift(@index);
    foreach $room (@index){
        local($idx, $name, $comment, $state, $pass) = split(/<>/, $room, 5);
        print <<ROW;
<option value="$idx">$idx $name</option>
ROW
    }

print <<FOOT;
</select>
</td></tr>
<tr><td>パスワード</td>
<td><input type="password" name="pass" size="15"></td>
</tr><tr><td></td><td><input type="submit" name="admin" value="削除"></td></tr>
</table>
</form>
</div>
FOOT
}

##### 関数：著作権文字列生成 #####
sub genCopyright() {
    local($copyRight)=<<EOM;
<p class="footer">
YADChat / 2007 &copy; 結城由羅＠世界忍者国 / BSD Lisence
</p>
EOM
    return $copyRight;
}

##### 関数：ロック処理 #####
sub lock {
    local($lockfile) = $_[0]; # ロックファイルは引数で指定
    local($lockmode) = $CONF{'lockmode'};
    local($retry) = $CONF{'retry'};
    local($timeout) = $CONF{'timeout'};
    local($errmsg) = "Lock ($lockfile) is busy";

    # 一定時間以上古いロックは削除する
    if (-e $lockfile) {
        ($mtime) = (stat($lockfile))[9];
        if ($mtime < time - $timeout) { &unlock($lockfile); }
    }
    # symlink関数式ロック
    if ($lockmode == 1) {
        while (!symlink(".", $lockfile)) {
            if (--$retry <= 0) { &error($errmsg); }
            sleep(1);
        }
    # mkdir関数式ロック
    } elsif ($lockmode == 2) {
        while (!mkdir($lockfile, 0755)) {
            if (--$retry <= 0) { &error($errmsg); }
            sleep(1);
        }
    }
}

##### 関数：ロック解除 #####
sub unlock {
    local($lockfile) = $_[0]; # ロックファイルは引数で指定
    local($lockmode) = $CONF{'lockmode'};

    # symlink形式
    if ($lockmode == 1) {
        unlink($lockfile);
    }

    # mkdir方式
    elsif ($lockmode == 2) {
        rmdir($lockfile);
    }

    # flock方式：未実装
    elsif ($lockmode == 3) {
    }
}

##### 関数：インデックス一覧取得 #####
sub readIndex {
    open(INDEX, "<$CONF{'indexdat'}") || die "Can't open $CONF{'indexdat'}: $!";
    flock(INDEX, LOCK_SH);
    local (@list) = <INDEX>;
    flock(INDEX, LOCK_NB);
    close(INDEX);
    @list;
}

##### 関数：インデックスファイル更新 #####
sub updateIndex {
    local(@index) = @_;

    open(INDEX, ">$CONF{'indexdat'}") || die "Can't open $CONF{'indexdat'}: $!";
    flock(INDEX, LOCK_EX);
    print INDEX @index;
    flock(INDEX, LOCK_NB);
    close(INDEX);
}

##### 関数：新規部屋作成 #####
sub createNewRoom {
    local($name, $comment) = @_;
    local(@index) = &readIndex();

    # 最終部屋番号の取得
    local($roomidx) = shift(@index);
    $roomidx++;

    # 部屋ファイルの作成
    local($roomfile) = "$CONF{'datdir'}/$roomidx.dat";
    open(NEW, ">$roomfile")  || die "Can't open $roomfile: $!";
    close(NEW);
    chmod (0640, $roomfile);

    # メンバファイルの作成
    local($memfile) = "$CONF{'memdir'}/$roomidx.dat";
    open(NEW, ">$memfile")  || die "Can't open $memfile: $!";
    close(NEW);
    chmod (0640, $memfile);

    # 新規部屋のインデックスファイルへの追加
    $line = "$roomidx<>$name<>$comment<>1<>$IN{'pass'}\n";
    unshift(@index, $line);

    $roomidx .= "\n";
    unshift(@index, $roomidx);

    updateIndex(@index);
}

##### 関数：部屋削除 #####
sub deleteRoom {
    local($delidx) = @_;
    local(@index) = &readIndex();

    local($roomfile) = "$CONF{'datdir'}/$roomidx.dat";
    local($memfile) = "$CONF{'memdir'}/$roomidx.dat";
    local($logfile) = "$CONF{'logdir'}/$roomidx.html";

    # 削除
    unlink($roomfile) if (-e $roomfile);
    unlink($memfile) if (-e $memfile);
    unlink($logfile) if (-e $logfile);

    @index = grep(!/$delidx<>/, @index);
    &updateIndex(@index);
}

##### 関数：部屋を閉じる #####
sub closeRoom {
    local($pass, $close) = @_;
    local(@index) = &readIndex();

    foreach $line (@index){
        if ($pass eq $CONF{'pass'}) {
            # 管理者権限でのクローズ
            if ($line =~ s/$close<>([^<>]+)<>([^<>]+)<>1<>(\S+)/$close<>$1<>$2<>0<>$3/) {
                if ($DEBUG) {
                    print "using admin pass...\n";
                }
                &genLogfile($close, &getRoomTitle($close, $1));
                &updateIndex(@index);
            }
        } else {
            # 作成者権限でのクローズ
            if ($line =~ s/$close<>([^<>]+)<>([^<>]+)<>1<>$pass/$close<>$1<>$2<>0<>$pass/) {
                if ($DEBUG) {
                    print "using creater's pass...\n";
                }
                &genLogfile($close, &getRoomTitle($close, $1));
                &updateIndex(@index);
            }
        }
    }
}

##### 関数：部屋タイトルの取得 #####
sub getRoomTitle {
    local($roomidx, $roomname) = @_;
    if ($roomname eq "") {
        local(@index) = &readIndex();
        foreach $line (@index) {
            if ($line =~ /$roomidx<>([^<>]+)<>([^<>]+)<>\d<>(\S+)/) {
                $roomname = $1;
            }
        }
    }
    return "$CONF{'title'}：$roomidx $roomname";
}

##### 関数：ログ閲覧リンク文字列 #####
sub genLogLinkString {
    local($room) = $_[0];
    local($str) = "";
    $str .= "[<a href=\"";
    $str .= $CONF{'chatcgi'}."?room=$room&mode=view&retime=0&line=200";
    $str .= "\" target=_blank>200行ログ</a>]";
    $str .= " [<a href=\"";
    $str .= $CONF{'chatcgi'}."?room=$room&mode=view&retime=0&line=500";
    $str .= "\" target=_blank>500行ログ</a>]";
    $str .= " [<a href=\"";
    $str .= $CONF{'chatcgi'}."?room=$room&mode=view&retime=0&line=1000";
    $str .= "\" target=_blank>1000行ログ</a>]";
    if ($CONF{'rplog'}) {
        $str .= " [<a href=\"";
        $str .= "rp/$room.txt";
        $str .= "\" target=_blank>RPログ</a>]";
    }
    return $str;
}

##### 関数：データを書式化 #####
sub formatDat {
    local($datfile) = $_[0];
    local($lnum) = $_[1];
    local($weight) = $_[2];
    local($reverse) = $_[3];

    # データ取得
    open(DAT,"<$datfile")
        || &printError("Open Error : datfile : $datfile: $!");
    flock(DAT, LOCK_SH);
    local(@lines) = <DAT>;
    flock(DAT, LOCK_NB);
    close(DAT);

    # 反転する場合は反転
    if ($reverse) {
        @lines = reverse(@lines);
    }

    local($i, $ret) = (0, "");
    foreach $line (@lines) {
        #print "$i: $line\n";
        $i++;
        if ($lnum ne 'max' && $i > $lnum) { last; }
        local($date,$name,$email,$comment,$color) = split(/<>/, $line);
        # 自動リンクONならばURL文字列置き換え$
        if ( $CONF{'autolink'} ) {
            # URL Auto Link
            $comment =~ s/(s?https?:\/\/[-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/<a href="\1" target="_blank">\1<\/a>/g;
        }
        $ret .= $query->hr;
        if ($weight eq 'light') {
            $ret .= "$name ＞ $comment ";
            $ret .= "($date)";
        } else {
            $ret .= "<font color=\"#$color\">$name ＞ $comment</font> ";
            $ret .= "<font color=\"#$rep_color\">($date)</font>";
        }
        $ret .= $query->br;
        $ret .= "\n";
    }

    return $ret;
}

##### 関数：ログファイル生成 #####
sub genLogfile {
    local($close, $title) = @_;
    local($datfile) = "$CONF{'datdir'}/$close.dat";
    local($memfile) = "$CONF{'memdir'}/$close.dat";
    local($logfile) = "$CONF{'logdir'}/$close.html";

    if ($DEBUG) {
        print "$datfile, $memfile, $logfile...\n";
    }
    # データファイルがない場合は何もせずに戻る
    if (! -e $datfile) {
        &printError("No such dat: $datfile");
        return;
    }

    # ログファイルに書き込み開始
    open(LOG, ">$logfile")
        || &error("Can't open : logfile : $logfile: $!");
    flock(LOG, LOCK_EX);

    if ($DEBUG) {
        print "writing $logfile...\n";
    }
    # ヘッダ部
    print LOG $query->start_html(-lang => $CONF{'lang'},
                 -encoding => $CONF{'charset'},
                 -head => meta({-http_equiv => 'Content-Type',
                                -content    => 'text/html; charset=' . $CONF{'charset'}}),
                 -title=>$title,
                 -style=>{-src=>"../$CONF{'chatstyle'}"});

    # タイトル
    print LOG $query->h1($title);

    if ($DEBUG) {
        print "writing data...\n";
    }
    # データ部
    print LOG &formatDat($datfile, 'max', 'normal', 'reverse');
    if ($DEBUG) {
        print "done...\n";
    }

    print LOG $query->hr ."\n";

    # コピーライト部
    print LOG &genCopyright();

    # HTML終わり
    print LOG $query->end_html;

    if ($DEBUG) {
        print "done...\n";
    }
    flock(LOG, LOCK_NB);
    close (LOG);

    if ($DEBUG) {
        print "$delete files...\n";
    }
    # データ＆メンバファイル削除
    unlink($datfile) if (-e $datfile);
    unlink($memfile) if (-e $memfile);
}

### End of yadchatlib
1;
