#!/usr/bin/python -W ignore::DeprecationWarning

""" rel_unpack.py - unpacks the contents of a release movie directory,
optionally checking SFV check sums prior to unpacking. 

This script should be used for educational purposes only. Downloading 
copyrighted movies from the Internet is illegal.

Usage: rel_unpack.py <release directory>

This script relies on the 'unrar' binary being installed. For SFV checking
to take place, the cfv Python module must be present.

Author: Per Rovegard

This code is in the public domain. You may use it for whatever you want,
as long as you do not hold the author responsible for the consequences of
its usage.

"""

import os
import sys
import re
import subprocess
from shutil import move

__version__ = "0.1"

DEBUG = 0
UNRAR = None
CHECK_SFV = True

# The cfv module is perhaps not present, requires cfv
# to be installed for SFV checking to take place.
try:
	import cfv
	cfv.config.setx("verbose", "q")
except ImportError:
	print "Did not find the cfv module, SFV checking is disabled."
	CHECK_SFV = False

def chomp(str):
	if str.endswith("\r\n"):
		return str[:-2]
	if str[-1] in "\r\n":
		return str[:-1]
	return str

def check_sfv(dir_path, sfv_file):
	print "Checking SFV checksum file '%s'..." % sfv_file
	# cfv requires us to set the current directory to where the
	# SFV file is.
	old_dir = os.getcwd()
	os.chdir(dir_path)

	cfv.test(sfv_file, "auto")

	os.chdir(old_dir)

	sfv_err = (cfv.stats.badcrc and 2) | (cfv.stats.badsize and 4) | (cfv.stats.notfound and 8) | (cfv.stats.ferror and 16) | (cfv.stats.unverified and 32) | (cfv.stats.cferror and 64)
	if sfv_err > 0:
		raise IOError("SFV check of '%s' failed, cfv error = %d." % (os.path.join(dir_path, sfv_file), sfv_err))

def move_files(from_dir, to_dir):
	print "Moving contents of '%s' to '%s'..." % (from_dir, to_dir)
	files = os.listdir(from_dir)
	full_files = [os.path.join(from_dir, file) for file in files]
	for f in full_files:
		if DEBUG > 0:
			print "Moving file '%s' to '%s'..." % (f, to_dir)
		move(f, to_dir)

def unpack_rar(dir_path, rarfile):
	# Get a list of the files currently in the directory, so that
	# we can compare after unpacking.
	old_listing = os.listdir(dir_path)

	print "Unpacking from archive '%s' in '%s'..." % (rarfile, dir_path)
	rar = os.path.join(dir_path, rarfile)
	args = "x -c- -o- -p- -y"
	p = subprocess.Popen(["%s %s %s %s" % (UNRAR, args, rar, dir_path)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
	if p.wait() > 0:
		err_str = ""
		line = p.stdout.readline()
		while line != "":
			err_str = err_str + line
			line = p.stdout.readline()
		raise IOError("Failed to unrar: %s" % err_str)

	new_listing = os.listdir(dir_path)
	new_files = [file for file in new_listing if not file in old_listing]
	if len(new_files) == 0:
		# Extraction resulted in no files. Error? Don't know, but
		# don't continue.
		return

	# Delete all the old files (but not directories).
	print "Deleting old files in '%s'..." % dir_path
	for file in old_listing:
		path = os.path.join(dir_path, file)
		if os.path.isfile(path):
			if DEBUG > 0:
				print "Deleting file '%s'..." % path
			os.unlink(path)

def check_rar(rarpath):
	p = subprocess.Popen(["file %s" % rarpath], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
	if p.wait() > 0:
		raise IOError("Failed to check RAR file type for '%s'." % rarpath)
	
	line = chomp(p.stdout.readline())
	
	if not "RAR archive data" in line:
		raise IOError("File '%s' does not seem to be a RAR file: %s" % (rarpath, line))

# dir_path: full directory path
def unpack_dir(dir_path):
	print "Entering directory '%s'." % dir_path
	contents = os.listdir(dir_path)
	rarfile = None
	sfvfiles = []
	files = [file for file in contents if os.path.isfile(os.path.join(dir_path, file))]
	dirs = [dir for dir in contents if os.path.isdir(os.path.join(dir_path, dir))]

	# Find out which RAR file to unpack, and make a note of any
	# SFV files in the directory.
	for file in files:
		if file.lower().endswith(".sfv"):
			sfvfiles.append(file)
			continue
		match = re.search(r"((\.part01)?\.rar)|\.001$", file, re.I)
		if not match is None and rarfile is None:
			rarfile = file

	# If we found any SFV files, check them now provided that we
	# have the capability to do so.
	if CHECK_SFV and len(sfvfiles) > 0:
		for sfv in sfvfiles:
			check_sfv(dir_path, sfv)

	if not rarfile is None:
		check_rar(os.path.join(dir_path, rarfile))
		unpack_rar(dir_path, rarfile)
	elif DEBUG > 0:
		print "No RAR file found in '%s', ignoring." % dir_path

	# See if there is any CDx directory we can recurse into.
	for dir in dirs:
		if not re.search(r"^CD\d+$", dir, re.I) is None:
			cd_dir = os.path.join(dir_path, dir)
			unpack_dir(cd_dir)
			move_files(cd_dir, dir_path)
			print "Deleting directory '%s'..." % cd_dir
			os.rmdir(cd_dir)

def init():
	# See if we can find unrar, need it for extracting...
	global UNRAR
	p = subprocess.Popen(["which unrar"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
	p.wait()
	line = chomp(p.stdout.readline())
	if line != "":
		UNRAR = line
	else:
		raise IOError("Cannot find the unrar command.")

def main(argv = None):
	if argv is None:
		argv = sys.argv[1:]

	if len(argv) == 0:
		raise Exception("Not enough arguments.")

	dir = argv[0]

	if not os.path.exists(dir) or not os.path.isdir(dir):
		raise IOError("%s does not exist or is not a directory." % dir)

	init()

	unpack_dir(os.path.realpath(dir))	

if __name__ == "__main__":
	try:
		main()
		sys.exit(0)
	except Exception, inst:
		print inst
		sys.exit(1)


