#! /usr/bin/env python
# -*- coding: Shift_JIS -*-

import sys
import os.path
import stat;
import time;
import md5
import sha
from optparse import OptionParser

#------------------------------------

class EnumerateDir:
	"""任意のフォルダを走査するクラスです。
	再帰的にサブフォルダを走査する場合はrecusriveをTrueに指定します。
	発見されたファイルはcallbackの引数として呼び出されます。"""

	def __init__(self, callback, recursive = 0):
		"""発見されたファイルを通知するコールバックと、再帰的にサブディレクトリを
		走査するか指定するrecursiveフラグを指定して構築します。"""
		
		self.__callback = callback
		self.__recursive = recursive
	
	
	def enum(self, dirname):
		"""指定したフォルダ以下を走査します。
		フォルダ以外が指定された場合は単にコールバックに渡されます。"""

		dirname = os.path.normpath( dirname )
		if os.path.isdir( dirname ):
			self.__enumdir( dirname )
		else:
			self.__callback( dirname )
	
	
	def __enumdir(self, dirname ):
		"""フォルダの走査を行います。"""
		
		dirc = os.listdir( dirname )
		dirc.sort()
		for filename in dirc:
			filename = os.path.normpath( dirname + "/" + filename );
			try:
				fs = os.stat( filename )
				if( stat.S_ISDIR( fs[ stat.ST_MODE ] ) ):
					if self.__recursive:
						self.__enumdir( filename  )
				else:
					self.__callback( filename )
			except OSError, e:
				print >>sys.stderr, e

#------------------------------------

class FileDigestCalcurator:
	"""ファイルを指定して、そのファイルのハッシュ値やファイルサイズ、更新日付
	などの情報を取得し、それらをコールバックに送信します。"""
	
	
	def __init__(self, callback, hashfactory ):
		"""ファイルの情報を受けるコールバックと、ハッシュモジュールへの参照を
		指定した構築します。"""
		
		self.__callback = callback;
		self.__hashfactory = hashfactory
	
	
	def getFileInfo( self, filename ):
		"""ファイル通知をうけます。指定したファイルのハッシュを計算し、
		また、ファイルサイズ、更新日付の情報を取得し、それをコールバックに
		送信します。"""
		
		fs = os.stat( filename )
		last_modified = fs[ stat.ST_MTIME ]
		file_size = fs[ stat.ST_SIZE ]
		hash = self.__hashfactory.new();
		try:
			theFile = file( filename, 'rb' )
			try:
				while True:
					chunk = theFile.read( 8192 )
					if len( chunk ) == 0:
						break
					hash.update( chunk )
			finally:
				theFile.close()
			self.__callback( filename, hashcode = hash.hexdigest(), file_size = file_size, last_modified = last_modified )
		except IOError, e:
			self.__callback( filename, hashcode = "*error:" + str( e ) )

#------------------------------------

class FileDigestOutput:
	"""ファイルのハッシュ値、ファイル名、ファイルサイズ、更新日時の情報を
	出力するためのクラス"""
	
	
	def __init__(self, output_filename = None, simple_format = 0):
		"""出力先ファイルおよびシンプルなフォーマットを使用するか示すフラグを
		指定した構築します。ファイルがNoneである場合は標準出力を用います。"""
		
		if output_filename == None:
			self.__output = sys.stdout
		else:
			self.__output = open( output_filename, "wt" )
		self.__simple_format = simple_format
	
	
	def hashprint(self, filename, hashcode, file_size = -1, last_modified = 0 ):
		"""ファイル名、ハッシュ値、ファイルサイズ、更新日付を指定して、
		それをフォーマットして出力します。"""
		
		if self.__simple_format:
			print >>self.__output, "%s *%s" % ( hashcode, filename )
		else:
			try:
				ctm = time.ctime( last_modified )
			except ValueError, e:
				ctm = str( e )
				print >>sys.stderr, filename, e
			print >>self.__output, "%s *%s *%d *%d (%s)" % ( hashcode, filename, file_size, last_modified, ctm )
	
	
	def dispose(self):
		"""ファイルを閉じるために呼び出します。"""
		
		self.__output.close()


#------------------------------------
usage="""使い方: %prog [options] [directory or file ...]
指定したディレクトリまたはファイルのハッシュ値とファイルサイズ、更新日時、ファイル名を出力します。
ディレクトリが指定された場合は、そのディレクトリのすべてのファイルを対象とします。
省略した場合はカレントディクトリを指定したものとみなされます。
※ ワイルドカードは使えません。
"""
parser = OptionParser( usage = usage )
parser.add_option(
        "-o", "--output",
	action="store", type="string",
        dest="output_filename",
	help = "指定したファイルへ出力します。"
	)
parser.add_option(
	"-r", "--recursive",
	action="store_true",
	dest="recursive",
	default=0,
	help="サブディレクトリを走査します。"
	)
parser.add_option(
	"-a", "--hash-algo",
	action="store", type="string",
	dest="hash_algo",
	default="sha",
	help="ハッシュのアルゴリズム(md5/sha1)を指定します。"
	)
parser.add_option(
	"-s", "--simple",
	action="store_true",
	dest="simple_format",
	default=0,
	help="Gnuのmd5sum/sha1sumのシンプルな形式で出力します。"
	)

(options, args) = parser.parse_args()

if len(args) == 0:
	args.append( "." )
args.sort()

algos = { "md5" : "md5", "sha" : "sha", "sha1" : "sha" }
if not options.hash_algo in algos :
	print >>sys.stderr, "サポートされていないアルゴリズムです。", options.hash_algo
	sys.exit(1)

hashalgo = eval( algos[ options.hash_algo ] )


fileDigestOutput = FileDigestOutput(
	output_filename = options.output_filename,
	simple_format   = options.simple_format
	)
try:
	fileDigestCalcurator = FileDigestCalcurator( fileDigestOutput.hashprint, hashalgo )

	enm = EnumerateDir(
		callback = fileDigestCalcurator.getFileInfo,
		recursive = options.recursive
		)

	for dirname in args:
	        enm.enum( dirname )
finally:
	fileDigestOutput.dispose()