#!/usr/local/bin/python
#coding:utf-8
#
# 東方紅魔郷スコアボード
# the Embodiment of Scarlet Devil Score board
# 
# Author: dl
# 
# License under Creative Commons License
# http://creativecommons.org/licenses/by-nc-sa/2.5/
# 

import os, sys, cgi, time, string, md5
import TH6Replay, rss10

###############################################################################
# 設定
###############################################################################
# マスターパスワード。この値は必ず変更してください。
adminPass = "admin"
# HTMLのfooterに表示する文字列
# 静的ページ（index.html, upload6.html）のfooterは手動で修正してください。
htmlFooter = "東方紅魔郷スコアボード"
# 設置アドレス。RSSの生成で使います。
# index.htmlの存在するディレクトリで指定します。
# 例：http://example.com/cgi-bin/th6score/
address = "http://example.com/cgi-bin/th6score/"

###############################################################################
# その他設定。特に変更の必要はありません。
###############################################################################
# RSSのファイル名
rssNew = "new6.rdf"
# 新着順のhtmlファイル名
htmlNew = "new6.html"
# 総合スコア順のhtmlファイル名
htmlScore = "score6.html"
# 難易度別のhtmlファイル名
htmlLevel = "level6.html"
# 難易度・キャラ別のhtmlファイル名
htmlLevelChar = "levelchar6.html"
# リプレイファイルのサイズ上限。単位はkb
maxReplaySize = 200
# データファイル名
dataFileName = "th6score.dat"
# Lock用のディレクトリ名
lockDir = "lock6"
# リプレイファイルの保存パス
replayDir = "replay6"
# 一時保存ファイル名
tempFileName = os.path.join(replayDir, "tmp.dat")

###############################################################################
# html parts
###############################################################################
HTTP_HEAD = "content-type: text/html; charset=utf-8\r\n\r\n"

HTML_HEAD = '''<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta http-equiv="Content-Style-Type" content="text/css" />
	<link rel="stylesheet" type="text/css" href="./style6.css" media="screen,print" title="th6" />
	<title>東方紅魔郷スコアボード</title>
</head>
<body>

<div class="title">
	<h1><a id="top" name="top">東方紅魔郷スコアボード</a></h1>
</div>
<div class="navi">
	<a href="./index.html">Index</a> &gt;&gt; %s
</div>
<div class="docbody">
<h2>%s</h2>
'''

HTML_FOOT = '''
</div>
<div class="footer">
	<address>
		%s
	</address>
</div>
</body>
</html>
''' % htmlFooter

NAVI_ABOVE = '''
<div class="pageNavi"><a href="#top">↑Above</a></div>
'''

TABLE_HEAD = '''
<table>
	<colgroup>
		<col class="colRank" />
		<col class="colScore" />
		<col class="colDrop" />
		<col class="colChar" />
		<col class="colLevel" />
		<col class="colDate" />
		<col class="colName" />
		<col class="colComment" />
		<col class="colReplay" />
	</colgroup>
	<tr>
		<th>順位</th>
		<th>スコア</th>
		<th>処理落率</th>
		<th>使用キャラ</th>
		<th>難易度</th>
		<th>プレイ日付</th>
		<th>名前</th>
		<th>コメント</th>
		<th>リプレイ</th>
	</tr>
'''

TABLE_BODY = '''
	<tr>
		<td>%s</td>
		<td>%s</td>
		<td>%s%%</td>
		<td>%s</td>
		<td>%s</td>
		<td>%s</td>
		<td>%s</td>
		<td>%s</td>
		<td>
			<a href="%s">%s</a>
			<a href="./th6score.cgi?task=delcheck&amp;id=%s">削除</a>
		</td>
	</tr>
'''

TABLE_FOOT = '''
</table>
'''

###############################################################################
# メッセージ
###############################################################################
MESSAGES = {
	"upload.ok" : "登録が正常に完了しました。",
	"delete.ok" : "削除が正常に完了しました。",
	"name.noinput" : "名前を入力してください。",
	"name.over" : "名前が長すぎます。",
	"replay.noinput" : "リプレイファイルを指定してください。",
	"replay.invalid" : "不正なリプレイファイルです。",
	"replay.over" : "リプレイファイルが大きすぎます。",
	"comment.noinput" : "コメントを入力してください。",
	"comment.over" : "コメントが長すぎます。",
	"busy" : "ただいま大変混み合っています。時間をおいて再度登録を実行してください。",
	"passwd.noinput" : "パスワードを入力してください。",
	"passwd.over" : "パスワードが長すぎます。",
	"passwd.missmatch" : "パスワードが一致しません。" ,
	"invalidcall" : "不正な呼び出しです。",
	"back" : "ブラウザの戻るボタンで戻ってください。",
	"nodata" : "登録データがありません。",
}

###############################################################################
# 定義
###############################################################################
# 難易度の一覧
LEVELS = [
	"Easy",
	"Normal",
	"Hard",
	"Lunatic",
	"Extra"
]
LEVEL_INDEX_EASY = 0
LEVEL_INDEX_NORMAL = 1
LEVEL_INDEX_HARD = 2
LEVEL_INDEX_LUNATIC = 3
LEVEL_INDEX_EXTRA = 4

# キャラクターの一覧
CHARS = [
	"博麗 霊夢（霊符）",
	"博麗 霊夢（夢符）",
	"霧雨 魔理沙（魔符）",
	"霧雨 魔理沙（恋符）"
]
CHARS_ABBR = [
	"霊符",
	"夢符",
	"魔符",
	"恋符"
]
CHAR_INDEX_REIMU_A = 0
CHAR_INDEX_REIMU_B = 1
CHAR_INDEX_MARISA_A = 2
CHAR_INDEX_MARISA_B = 3


###############################################################################
# funcs
###############################################################################

# mkdirでLock実行
def dirLock():
	try:
		os.mkdir(lockDir)
		return True
	except:
		return False

# Lock実行。混んでる場合は2秒*2回まで待つ。
def doLock():
	if not dirLock():
		time.sleep(2)
		if not dirLock():
			time.sleep(2)
			if not dirLock():
				# give up
				return False
	return True

# Lock解除
def unLock():
	os.rmdir(lockDir)

# アップロードファイルの一時保存
# 入力チェックは事前に済ませておくこと。
# return 0:成功 1:サイズオーバー 2:不正なリプレイ
def saveTemp(request):
	if not os.path.exists(replayDir):
		os.mkdir(replayDir)
	
	fileArg = request["replay"]
	retCode = 0
	try:
		buf = ""
		fout = open(tempFileName, "wb")
		count = 0
		while(True):
			buf = fileArg.file.read(1024)
			if not buf:
				break
			# 初回はヘッダチェック
			if count == 0:
				if len(buf) <= 4:
					retCode = 2
					break
				if not buf[0:4] == "T6RP":
					retCode = 2
					break
			fout.write(buf)
			count += 1
			if count > maxReplaySize:
				retCode = 1
				break
		if count == 0:
			retCode = 2
	finally:
		if fout:
			fout.close()
	return retCode

# htmlエスケープ
def escapeHtml(s):
	s = string.replace(s, "\t", "    ")
	s = string.replace(s, "&", "&amp;")
	s = string.replace(s, "\"", "&quot;")
	s = string.replace(s, "<", "&lt;")
	s = string.replace(s, ">", "&gt;")
	s = string.replace(s, "\r", "")
	s = string.replace(s, "\n", "<br />")
	return s

# 10桁になるよう前方を0埋め
def zeroSup(s):
	while len(s) < 10:
		s = "0" + s
	return s

# スコアの3桁ごとにカンマを追加
def addComma(s):
	index = 1
	slen = len(s)
	ret = ""
	while index <= slen:
		ret = s[slen-index] + ret
		if index % 3 == 0 and index != slen:
			ret = "," + ret
		index += 1
	return ret

# パーサーから取得した難易度の数値変換
# LEVELSのindexと対応
def getNumLevel(s):
	if s == "Easy":
		return LEVEL_INDEX_EASY
	elif s == "Normal":
		return LEVEL_INDEX_NORMAL
	elif s == "Hard":
		return LEVEL_INDEX_HARD
	elif s == "Lunatic":
		return LEVEL_INDEX_LUNATIC
	else:
		return LEVEL_INDEX_EXTRA

# パーサーから取得したキャラの数値変換
# CHARSのindexと対応
def getNumChar(s):
	if(s == "ReimuA"):
		return CHAR_INDEX_REIMU_A
	elif(s == "ReimuB"):
		return CHAR_INDEX_REIMU_B
	elif(s == "MarisaA"):
		return CHAR_INDEX_MARISA_A
	else:
		return CHAR_INDEX_MARISA_B

# 指定されたパスワードのhashを取得
def getDigest(passwd):
	return md5.new(passwd).hexdigest()

# 現在時刻のタイムスタンプ取得。 YYYY-MM-DD HH24:MI:SS
def getCurrentTimestamp():
	tm = time.localtime(time.time())
	return time.strftime("%Y/%m/%d %H:%M:%S", tm)

# 末尾の0x00を削除
def trimNull(s):
	while(True):
		if(len(s) > 0):
			if(s[-1] == "\x00"):
				s = s[:-1]
			else:
				break
		else:
			break
	return s

# データファイルを表現するクラス
class DataFile:
	def __init__(self):
		self.dataDict = {}
		self.dataKeys = []
		if os.path.exists(dataFileName):
			fin = None
			try:
				fin = open(dataFileName, "r")
				line = None
				while(True):
					line = fin.readline()
					if not line:
						break
					repInfo = ReplayInfo()
					repInfo.loadDataInfo(string.strip(line))
					self.dataDict[repInfo.getDataId()] = repInfo
			finally:
				if fin:
					fin.close()
		self.dataKeys = self.dataDict.keys()
	
	# dataIdの配列を取得。
	# ソート順は直前に呼ばれたsortメソッドの種類に準ずる
	def keys(self):
		return self.dataKeys
	
	# キーを新着順にソート
	def sortNew(self):
		self.dataKeys = self.dataDict.keys()
		self.dataKeys.sort()
		self.dataKeys.reverse()
	
	# キーをスコアの高い順にソート
	def sortScore(self):
		self.dataKeys = self.dataDict.keys()
		kk = []
		for key in self.dataKeys:
			kk.append(zeroSup(self.dataDict[key].getScore()) + "\t" + key)
		kk.sort()
		kk.reverse()
		self.dataKeys = []
		for k in kk:
			self.dataKeys.append(string.split(k, "\t")[1])
	
	# 全てのデータをReplayInfoの配列で取得
	# ソート順は直前に呼ばれたsortメソッドの種類に準ずる
	def getAllData(self):
		ret = []
		for key in self.dataKeys:
			ret.append(self.dataDict[key])
		return ret
	
	# 指定されたキャラ、難易度のデータをReplayInfoの配列で取得
	# ソート順は直前に呼ばれたsortメソッドの種類に準ずる
	# levelとcharはそれぞれLEVEL_INDEX_*とCHAR_INDEX_*で指定する
	# charを指定しない場合はlevelのみで絞りこんだデータが取得される。
	def getPartData(self, level, char=-1):
		ret = []
		for key in self.dataKeys:
			if char == -1:
				if self.dataDict[key].getLevel() == str(level):
					ret.append(self.dataDict[key])
			else:
				if self.dataDict[key].getLevel() == str(level) and \
						self.dataDict[key].getChar() == str(char) :
					ret.append(self.dataDict[key])
		return ret
	
	# 次のIDを取得。形式は10進数値で、10桁の文字列型。
	def getNextDataId(self):
		index = 0
		if len(self.dataDict.keys()) == 0:
			return zeroSup("0")
		self.sortNew()
		current = self.dataDict[self.dataKeys[0]].getDataId()
		return zeroSup(str(int(current) + 1))
	
	# リプレイを追加
	def add(self, replayInfo):
		self.dataDict[replayInfo.getDataId()] = replayInfo
	
	# 指定されたIdのリプレイ情報を取得
	def get(self, dataId):
		return self.dataDict[dataId]
	
	# リプレイを削除
	def remove(self, dataId):
		self.dataDict.pop(dataId)
	
	# 現在の内容でデータファイルを更新
	def replace(self):
		fout = None
		try:
			fout = open(dataFileName + ".tmp", "w")
			self.sortNew()
			for key in self.dataKeys:
				fout.write(self.dataDict[key].getDataLine() + "\n")
		finally:
			if fout:
				fout.close()
		
		# バックアップ削除
		if os.path.exists(dataFileName + ".old"):
			os.remove(dataFileName + ".old")
		# 現在のデータファイルを待避
		if os.path.exists(dataFileName):
			os.rename(dataFileName, dataFileName + ".old")
		# データファイル作成
		os.rename(dataFileName + ".tmp", dataFileName)


# replayを表現するクラス
class ReplayInfo:
	def __init__(self):
		# 10進の連番ID。10桁に0埋めされた文字列型
		self.dataId = ""
		# リプレイファイルのプレイ日付
		self.date = ""
		# リプレイファイルのプレイヤー名
		self.player = ""
		# 使用キャラ。0-5
		self.char = ""
		# スコア
		self.score = ""
		# 難易度 0-5
		self.level = ""
		# version
		self.version = ""
		# 処理落ち率
		self.drop = ""
		# コメント
		self.comment = ""
		# アップロード日付 yyyy-mm-dd hh24:mi:ss
		self.uploadDate = ""
		# パスワードのdigest
		self.passwd = ""
		# プレイヤーの名前
		self.name = ""
	
	# リプレイファイルから情報を読み取る
	# return: 取得に失敗した場合False
	def loadFileInfo(self, filepath):
		info = TH6Replay.TH6Replay_getReplayFileInfo(filepath)
		self.date = escapeHtml(trimNull(info[0]))
		self.player = escapeHtml(trimNull(info[1]))
		self.char = escapeHtml(str(getNumChar(trimNull(info[2]))))
		self.score = escapeHtml(trimNull(info[3]))
		self.level = escapeHtml(str(getNumLevel(trimNull(info[4]))))
		self.version = escapeHtml(trimNull(info[5]))
		self.drop = escapeHtml(trimNull(info[6]))
		if(len(self.date) > 0):
			return True
		else:
			return False
	
	# データ行から情報を読み取る
	def loadDataInfo(self, line):
		info = string.split(line, "\t")
		self.dataId = info[0]
		self.date = info[1]
		self.player = info[2]
		self.char = info[3]
		self.score = info[4]
		self.level = info[5]
		self.version = info[6]
		self.drop = info[7]
		self.name = info[8]
		self.comment = info[9]
		self.uploadDate = info[10]
		self.passwd = info[11]
	
	# 自身の内容からデータファイルの1行分を生成
	def getDataLine(self):
		buf = "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s"
		return buf % ( \
			self.dataId, \
			self.date, \
			self.player, \
			self.char, \
			self.score, \
			self.level, \
			self.version, \
			self.drop, \
			self.name, \
			self.comment, \
			self.uploadDate, \
			self.passwd)
	
	
	# 自身の内容からRSSのTitleを生成
	def getRssTitle(self):
		return "%s %s (%s, %s, %s)" % ( \
			self.uploadDate, \
			self.name, \
			LEVELS[int(self.level)], \
			CHARS_ABBR[int(self.char)], \
			addComma(self.score) )
	
	# 自身の内容からRSSのLinkを生成
	def getRssLink(self):
		return "%s%s/%s" % ( \
			address, \
			replayDir, \
			self.getReplayFileName())
	
	# rss用の情報を取得
	def getRssDescription(self):
		buf = '''
登録日：%s
名前：%s
難易度：%s
使用キャラ：%s
スコア：%s
処理落ち率:%s
プレイ日付：%s
コメント：%s
'''
		return buf % (\
			self.uploadDate, \
			self.name, \
			LEVELS[int(self.level)], \
			CHARS[int(self.char)], \
			addComma(self.score), \
			self.drop, \
			self.date, \
			string.replace(self.comment, "<br />", "\r\n"))
	
	# 10進数値からリプレイファイル名を取得
	# 4桁の16進文字列をIDとし、th6_udxxxx.rpyの形式
	def getReplayFileName(self):
		s = hex(int(self.dataId))[2:]
		while( len(s) < 4 ):
			s = "0" + s
		return "th6_ud" + s + ".rpy"
	
	def getDataId(self):
		return self.dataId
	def setDataId(self, dataId):
		self.dataId = dataId
	
	def getDate(self):
		return self.date
	def setDate(self, date):
		self.date = date
	
	def getPlayer(self):
		return self.player
	def setPlayer(self, player):
		self.player = player
	
	def getChar(self):
		return self.char
	def setChar(self, char):
		self.char = char
	
	def getScore(self):
		return self.score
	def setScore(self, score):
		self.score = score
	
	def getLevel(self):
		return self.level
	def setLevel(self, level):
		self.level = level
	
	def getVersion(self):
		return self.version
	def setVersion(self, version):
		self.version = version
	
	def getDrop(self):
		return self.drop
	def setDrop(self, drop):
		self.drop = drop
	
	def getComment(self):
		return self.comment
	def setComment(self, comment):
		self.comment = comment
	
	def getUploadDate(self):
		return self.uploadDate
	def setUploadDate(self, uploadDate):
		self.uploadDate = uploadDate
	
	def getPasswd(self):
		return self.passwd
	def setPasswd(self, passwd):
		self.passwd = passwd
	
	def getName(self):
		return self.name
	def setName(self, name):
		self.name = name


# 表示用HTMLの生成
def writeHtml(dataFile):
	writeHtmlNew(dataFile)
	writeHtmlScore(dataFile)
	writeHtmlLevel(dataFile)
	writeHtmlLevelChar(dataFile)
	writeRSS(dataFile)


# リプレイ1行分のテーブルアイテムを出力
def writeTableItem(fout, replayInfo, ranking):
	upd = replayInfo.getUploadDate()
	upd = upd[0:string.find(upd, " ")]
	fout.write(TABLE_BODY % ( \
		ranking, \
		addComma(replayInfo.getScore()), \
		replayInfo.getDrop(), \
		CHARS[int(replayInfo.getChar())], \
		LEVELS[int(replayInfo.getLevel())], \
		replayInfo.getDate(), \
		replayInfo.getName(), \
		replayInfo.getComment(), \
		replayDir + "/" + replayInfo.getReplayFileName(), \
		upd, \
		replayInfo.getDataId() \
	))

# 表示用HTMLの生成 新着順RSS
def writeRSS(dataFile):
	fout = None
	try:
		fout = open(rssNew + ".tmp", "w")
		rss = rss10.RSS10(\
			address + rssNew, \
			"東方紅魔郷スコアボード", \
			address, \
			"東方紅魔郷のスコアボードです" \
			)
		
		dataFile.sortNew()
		keys = dataFile.keys()
		
		if len(keys) == 0:
			rss.addLi([\
				address + htmlLevelChar, \
				MESSAGES["nodata"],
				address + htmlLevelChar, \
				MESSAGES["nodata"] \
				])
		else:
			itemCount = 0
			for key in keys:
				rep = dataFile.get(key)
				rss.addLi([\
					rep.getRssLink(), \
					rep.getRssTitle(),\
					rep.getRssLink(), \
					rep.getRssDescription()])
				itemCount += 1
				if itemCount >= 10:
					break
		fout.write(rss.getRSS())
	finally:
		if fout:
			fout.close()
	if os.path.exists(rssNew):
		os.remove(rssNew)
	os.rename(rssNew + ".tmp", rssNew)

# 表示用HTMLの生成 新着順一覧
def writeHtmlNew(dataFile):
	fout = None
	try:
		fout = open(htmlNew + ".tmp", "w")
		fout.write(HTML_HEAD % ("新着順一覧", "新着順一覧"))
		dataFile.sortNew()
		keys = dataFile.keys()
		
		if len(keys) == 0:
			fout.write("<p>" + MESSAGES["nodata"] + "</p>")
		else:
			fout.write(TABLE_HEAD)
			for key in keys:
				rep = dataFile.get(key)
				writeTableItem(fout, rep, "-")
			fout.write(TABLE_FOOT)
			fout.write(NAVI_ABOVE)
		fout.write(HTML_FOOT)
	finally:
		if fout:
			fout.close()
	if os.path.exists(htmlNew):
		os.remove(htmlNew)
	os.rename(htmlNew + ".tmp", htmlNew)


# 表示用HTMLの生成 スコア順一覧
def writeHtmlScore(dataFile):
	fout = None
	try:
		fout = open(htmlScore + ".tmp", "w")
		fout.write(HTML_HEAD % ("スコア順一覧", "スコア順一覧"))
		dataFile.sortScore()
		keys = dataFile.keys()
		
		if len(keys) == 0:
			fout.write("<p>" + MESSAGES["nodata"] + "</p>")
		else:
			fout.write(TABLE_HEAD)
			index = 1
			for key in keys:
				rep = dataFile.get(key)
				writeTableItem(fout, rep, index)
				index += 1
			fout.write(TABLE_FOOT)
			fout.write(NAVI_ABOVE)
		fout.write(HTML_FOOT)
	finally:
		if fout:
			fout.close()
	if os.path.exists(htmlScore):
		os.remove(htmlScore)
	os.rename(htmlScore + ".tmp", htmlScore)


# 表示用HTMLの生成 難易度別
def writeHtmlLevel(dataFile):
	fout = None
	try:
		fout = open(htmlLevel + ".tmp", "w")
		fout.write(HTML_HEAD % ("難易度別一覧", "難易度別一覧"))
		dataFile.sortScore()
		
		for level in range(len(LEVELS)):
			fout.write("<h3>%s</h3>" % LEVELS[level])
			
			reps = dataFile.getPartData(level)
			if len(reps) == 0:
				fout.write("<p>" + MESSAGES["nodata"] + "</p>")
			else:
				fout.write(TABLE_HEAD)
				index = 1
				for rep in reps:
					writeTableItem(fout, rep, index)
					index += 1
				fout.write(TABLE_FOOT)
			fout.write(NAVI_ABOVE)
		fout.write(HTML_FOOT)
	finally:
		if fout:
			fout.close()
	if os.path.exists(htmlLevel):
		os.remove(htmlLevel)
	os.rename(htmlLevel + ".tmp", htmlLevel)


# 表示用HTMLの生成 難易度・キャラ別
def writeHtmlLevelChar(dataFile):
	fout = None
	try:
		fout = open(htmlLevelChar + ".tmp", "w")
		fout.write(\
			HTML_HEAD % ("難易度・キャラ別一覧", "難易度・キャラ別一覧"))
		dataFile.sortScore()
		
		for level in range(len(LEVELS)):
			fout.write("<h3>%s</h3>" % LEVELS[level])
			for char in range(len(CHARS)):
				fout.write("<h4>%s</h4>" % CHARS[char])
				reps = dataFile.getPartData(level, char)
				if len(reps) == 0:
					fout.write("<p>" + MESSAGES["nodata"] + "</p>")
				else:
					fout.write(TABLE_HEAD)
					index = 1
					for rep in reps:
						writeTableItem(fout, rep, index)
						index += 1
					fout.write(TABLE_FOOT)
			fout.write(NAVI_ABOVE)
		fout.write(HTML_FOOT)
	finally:
		if fout:
			fout.close()
	if os.path.exists(htmlLevelChar):
		os.remove(htmlLevelChar)
	os.rename(htmlLevelChar + ".tmp", htmlLevelChar)

# 小数点以下2桁で切りつめ
def trimDrop(drop):
	drop = str(drop)
	index = string.find(drop, ".")
	if(index < 0):
		return drop
	length = len(drop)
	ret = drop
	if(index >= 0 and index+3 <= length):
		ret = drop[0:index+3]
	return ret

# パラメータのチェックを実行する。
# エラーがある場合はメッセージを配列に格納して返す
def inputCheck(request):
	message = []
	task = ""
	name = ""
	comment = ""
	passwd = ""
	dataId = ""
	year = ""
	month = ""
	day = ""
	drop = ""
	char = ""
	level = ""
	
	# 不正な呼び出し
	if request.has_key("task"):
		task = request["task"].value
	else:
		message.append(MESSAGES["invalidcall"])
		return message
	
	# 新規登録の場合
	if task == "upload":
		# 名前
		if request.has_key("name") and len(request["name"].value) > 0:
			name = request["name"].value
			if len(name) > 50:
				message.append(MESSAGES["name.over"])
		else:
			message.append(MESSAGES["name.noinput"])
		
		# パスワード
		if request.has_key("passwd") and len(request["passwd"].value) > 0:
			passwd = request["passwd"].value
			if len(passwd) > 20:
				message.append(MESSAGES["passwd.over"])
		else:
			message.append(MESSAGES["passwd.noinput"])
		
		# コメント
		if request.has_key("comment") and len(request["comment"].value) > 0:
			comment = request["comment"].value
			if len(comment) > 1000:
				message.append(MESSAGES["comment.over"])
		else:
			message.append(MESSAGES["comment.noinput"])
		
		# リプレイファイル
		if request.has_key("replay"):
			item = request["replay"]
			if item.file and len(item.filename) > 0 :
				#ok
				pass
			else:
				message.append(MESSAGES["replay.noinput"])
		else:
			message.append("aaa" +MESSAGES["replay.noinput"])
	# 削除の場合
	elif task == "delete":
		
		if request.has_key("id") and len(request["id"].value) > 0:
			dataId = request["id"].value
		else:
			message.append(MESSAGES["invalidcall"])
			return message
		
		if request.has_key("passwd") and len(request["passwd"].value) > 0:
			passwd = request["passwd"].value
		else:
			message.append(MESSAGES["passwd.noinput"])
	# 削除確認の場合
	elif task == "delcheck":
		if request.has_key("id") and len(request["id"].value) > 0:
			dataId = request["id"].value
		else:
			message.append(MESSAGES["invalidcall"])
			return message
	# 不正な呼び出し
	else:
		message.append(MESSAGES["invalidcall"])
	
	return message


# エラーメッセージを表示して終了
def showErrorAndExit(message):
	print HTTP_HEAD
	print HTML_HEAD % ("入力エラー", "入力エラー")
	for mes in message:
		print "<p>%s</p>" % mes
	print "<div class=\"pageNavi\">%s</div>" % MESSAGES["back"]
	print HTML_FOOT
	sys.exit(0)

# 2桁になるよう前方を0埋め
def zeroSup2(s):
	while(len(s) <= 1):
		s = "0" + s
	return s

# 新規登録処理
def doUpload(request):
	message = []
	
	player = "-"
	name = request["name"].value
	comment = request["comment"].value
	passwd = request["passwd"].value
	
	isLocked = False
	try:
		# lock実行
		isLocked = doLock()
		if isLocked:
			# アップロード実行
			retCode = saveTemp(request)
			if retCode == 1:
				# サイズオーバー
				sys.stderr.write("invalid request: replay size over.")
				message.append(MESSAGES["replay.over"])
			elif retCode == 2:
				# 不正なリプレイ
				sys.stderr.write("invalid request: invalid replay header.")
				message.append(MESSAGES["replay.invalid"])
			else:
				rep = ReplayInfo()
				# リプレイファイルから情報取得
				if not rep.loadFileInfo(tempFileName):
					sys.stderr.write(\
						"invalid request: can't parse replay info.")
					# リプレイファイルが不正な場合はエラー
					message.append(MESSAGES["replay.invalid"])
				else:
					# データファイル更新
					dataFile = DataFile()
					rep.setDataId(dataFile.getNextDataId())
					rep.setName(escapeHtml(name))
					rep.setComment(escapeHtml(comment))
					rep.setUploadDate(getCurrentTimestamp())
					rep.setPasswd(getDigest(passwd))
					rep.setDate(rep.getDate())
					dataFile.add(rep)
					dataFile.replace()
					
					# replay保存
					os.rename(\
						tempFileName, \
						os.path.join(replayDir, rep.getReplayFileName()) )
				
					# html生成
					writeHtml(dataFile)
		else:
			# 混んでて登録できない
			sys.stderr.write("invalid request: busy. lock file was detected.")
			message.append(MESSAGES["busy"])
	finally:
		if isLocked:
			unLock()
	
	# エラーがある場合はここで終了
	if len(message) > 0:
		showErrorAndExit(message)
	
	# 結果出力
	print HTTP_HEAD
	print HTML_HEAD % ("新規スコア登録", "新規スコア登録")
	print "<p>" + MESSAGES["upload.ok"] + "</p>"
	print HTML_FOOT


# 削除情報入力画面
def doDelCheck(request):
	dataId = request["id"].value
	# 結果出力
	print HTTP_HEAD
	print HTML_HEAD % ("削除", "削除")
	out = '''<form name="fm" action="./th6score.cgi" method="post">
パスワード：<input type="password" name="passwd" value="" />
<input type="submit" name="sub" value="削除実行" / >
<input type="hidden" name="id" value="%s" />
<input type="hidden" name="task" value="delete" />
</form>
'''
	print out % dataId
	print HTML_FOOT


# 削除実行
def doDelete(request):
	message = []
	dataId = request["id"].value
	passwd = request["passwd"].value
	isLocked = False
	try:
		# lock実行
		isLocked = doLock()
		if isLocked:
			# データファイル読み込み
			dataFile = DataFile()
			keys = dataFile.keys()
			if keys.count(dataId) > 0:
				rep = dataFile.get(dataId)
				# pass check
				if rep.getPasswd() == getDigest(passwd) or \
						passwd == adminPass:
					
					# replay削除
					os.remove(os.path.join(replayDir, rep.getReplayFileName()))
					
					# データファイル更新
					dataFile.remove(rep.getDataId())
					dataFile.replace()
					
					# html生成
					writeHtml(dataFile)
				else:
					# パスワードが一致しない
					message.append(MESSAGES["passwd.missmatch"])
			else:
				# idが存在しない
				message.append(MESSAGES["invalidcall"])
		else:
			# 混んでて登録できない
			message.append(MESSAGES["busy"])
	finally:
		if isLocked:
			unLock()
	
	# エラーがある場合はここで終了
	if len(message) > 0:
		showErrorAndExit(message)
	
	# 結果出力
	print HTTP_HEAD
	print HTML_HEAD % ("削除", "削除")
	print "<p>%s</p>" % MESSAGES["delete.ok"]
	print HTML_FOOT


###############################################################################
# main
###############################################################################

# ms-windows用
try:
	import msvcrt
	msvcrt.setmode(0, os.O_BINARY)
	msvcrt.setmode(1, os.O_BINARY)
except ImportError:
	pass

# get form
request = cgi.FieldStorage()

# 入力チェック
message = inputCheck(request)
# エラーがある場合はここで終了
if len(message) > 0:
	showErrorAndExit(message)
else:
	# タスク判別
	task = request["task"].value
	if task == "upload":
		doUpload(request)
	elif task == "delcheck":
		doDelCheck(request)
	elif task == "delete":
		doDelete(request)
	else:
		message.append(MESSAGES["invalidcall"])
		showErrorAndExit(message)
# 終了
sys.exit(0)


# end