if __name__ == '__main__':
	import findrox; findrox.version(1, 99, 11)
import os, sys
from support import shell_escape, Tmp
import rox
from rox.processes import PipeThroughCommand

current_command = None

def pipe_through_command(command, src, dst):
	global current_command
	assert not current_command
	try:
		src.seek(0)
	except:
		pass
	current_command = PipeThroughCommand(command, src, dst)
	try:
		current_command.wait()
	finally:
		current_command = None

operations = []
class Operation:
	add_extension = False
	
	def __init__(self, extension):
		operations.append(self)
		self.extension = extension
	
	def can_handle(self, data):
		return isinstance(data, FileData)

	def save_to_stream(self, data, stream):
		pipe_through_command(self.command, data.source, stream)

class Compress(Operation):
	"Compress a stream into another stream."
	add_extension = True

	def __init__(self, extension, command, type):
		Operation.__init__(self, extension)
		self.command = command
		self.type = type
	
	def __str__(self):
		return _('Compress as .%s') % self.extension

class Decompress(Operation):
	"Decompress a stream into another stream."
	type = 'text/plain'
	
	def __init__(self, extension, command):
		Operation.__init__(self, extension)
		self.command = command

	def __str__(self):
		return _('Decompress .%s') % self.extension

class Extract(Operation):
	"Extract an archive to a directory."
	type = 'inode/directory'

	def __init__(self, extension, command):
		"If command has a %s then the source path is inserted, else uses stdin."
		Operation.__init__(self, extension)
		self.command = command

	def __str__(self):
		return _('Extract from a .%s') % self.extension
	
	def save_to_stream(self, data, stream):
		raise Exception(_('This operation creates a directory, so you have '
				'to drag to a filer window on the local machine'))
	
	def save_to_file(self, data, path):
		if os.path.exists(path):
			if not os.path.isdir(path):
				raise Exception(_("'%s' already exists and is not a directory!") %
						path)
		if not os.path.exists(path):
			os.mkdir(path)
		os.chdir(path)
		command = self.command
		source = data.source
		if command.find("'%s'") != -1:
			command = command % shell_escape(source.name)
			source = None
		try:
			pipe_through_command(command, source, None)
		finally:
			try:
				os.rmdir(path)	# Will only succeed if it's empty
			except:
				pass
			if os.path.exists(path):
				self.pull_up(path)
	
	def pull_up(self, path):
		# If we created only a single subdirectory, move it up.
		dirs = os.listdir(path)
		if len(dirs) != 1:
			return
		dir = dirs[0]
		unneeded_path = os.path.join(path, dir)
		if not os.path.isdir(unneeded_path):
			return
		import random
		tmp_path = os.path.join(path, 'tmp-' + `random.randint(0, 100000)`)
		os.rename(unneeded_path, tmp_path)
		for file in os.listdir(tmp_path):
			os.rename(os.path.join(tmp_path, file), os.path.join(path, file))
		os.rmdir(tmp_path)

class Archive(Operation):
	"Create an archive from a directory."
	add_extension = True

	def __init__(self, extension, command, type):
		assert command.find("'%s'") != -1

		Operation.__init__(self, extension)
		self.command = command
		self.type = type

	def __str__(self):
		return _('Create .%s archive') % self.extension

	def can_handle(self, data):
		return isinstance(data, DirData)

	def save_to_stream(self, data, stream):
		os.chdir(os.path.dirname(data.path))
		command = self.command % shell_escape(os.path.basename(data.path))
		pipe_through_command(command, None, stream)

tgz = Extract('tgz',	"gunzip -c - | tar xf -")
tbz = Extract('tar.bz2', "bunzip2 -c - | tar xf -")
tarz = Extract('tar.Z', "uncompress -c - | tar xf -")
rar = Extract('rar',	"rar x '%s'")
ace = Extract('ace',   "unace x '%s'")
tar = Extract('tar',	"tar xf -")
rpm = Extract('rpm',	"rpm2cpio - | cpio -id --quiet")
cpio = Extract('cpio',	"cpio -id --quiet")
deb = Extract('deb', "ar x '%s'")
zip = Extract('zip', "unzip -q '%s'")
jar = Extract('jar', "unzip -q '%s'")

make_tgz = Archive('tgz', "tar cf - '%s' | gzip", 'application/x-compressed-tar')
Archive('tar.gz',  "tar cf - '%s' | gzip", 'application/x-compressed-tar')
Archive('tar.bz2', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar')
Archive('zip',	   "zip -qr - '%s'", 'application/zip'),
Archive('jar',	   "zip -qr - '%s'", 'application/x-jar')
Archive('tar',	   "tar cf - '%s'", 'application/x-tar')

# Note: these go afterwards so that .tar.gz matches before .gz
make_gz = Compress('gz', "gzip -c -", 'application/x-gzip')
Compress('bz2', "bzip2 -c -", 'application/x-bzip')
Compress('uue', "uuencode /dev/stdout", 'application/x-uuencoded')

gz = Decompress('gz',   "gunzip -c -")
bz2 = Decompress('bz2',  "bunzip2 -ck -")
uue = Decompress('uue',  "uudecode -o /dev/stdout")
z   = Decompress('Z',   "uncompress -c -")


# Can bzip2 read bzip files?

aliases = {
	'tar.gz': 'tgz',
	'tar.bz': 'tar.bz2',
	'tbz': 'tar.bz2',
	'bz': 'bz2'
}

known_extensions = {}
for x in operations:
	try:
		known_extensions[x.extension] = None
	except AttributeError:
		pass

class FileData:
	"A file on the local filesystem."
	mode = None
	def __init__(self, path):
		self.path = path
	
		if path == '-':
			source = sys.stdin
		else:
			try:
				source = file(path)
				self.mode = os.stat(path).st_mode
			except:
				rox.report_exception()
				sys.exit(1)

		self.path = path
		start = source.read(300)
		try:
			if source is sys.stdin:
				raise Exception("Always copy stdin!")
			source.seek(0)
			self.source = source
		except:
			# Input is not a regular, local, seekable file, so copy it
			# to a local temp file.
			import shutil
			tmp = Tmp()
			tmp.write(start)
			tmp.flush()
			shutil.copyfileobj(source, tmp)
			tmp.seek(0)
			tmp.flush()
			self.source = tmp
		self.default = self.guess_format(start)

		if path == '-':
			name = 'Data'
		else:
			name = path
			for ext in known_extensions:
				if path.endswith('.' + ext):
					new = path[:-len(ext)-1]
					if len(new) < len(name):
						name = new
		if self.default.add_extension:
			name += '.' + self.default.extension

		if name == path:
			# Default name is same as input. Change it somehow...
			if '.' in os.path.basename(name):
				name = name[:name.rindex('.')]
			else:
				name += '.unpacked'
				
		self.default_name = name

	def guess_format(self, data):
		"Return a good default Operation, judging by the first 300 bytes or so."
		l = len(data)
		def string(offset, match):
			return data[offset:offset + len(match)] == match
		def short(offset, match):
			if l > offset + 1:
				a = data[offset]
				b = data[offset + 1]
				return ((a == match & 0xff) and (b == (match >> 8))) or \
					(b == match & 0xff) and (a == (match >> 8))
			return 0

		# Archives
		if string(257, 'ustar\0') or string(257, 'ustar\040\040\0'):
			return tar
		if short(0, 070707) or short(0, 0143561) or string(0, '070707') or \
		   string(0, '070701') or string(0, '070702'):
			return cpio
		if string(0, '!<arch>') or string(0, '\\<ar>') or string(0, '<ar>'):
			if string(7, '\ndebian'):
				return deb
		if string(0, 'Rar!'): return rar
		if string(7, '**ACE**'): return ace
		if string(0, 'PK\003\004'): return zip
		if string(0, 'PK00'): return zip
		if string(0, '\xed\xab\xee\xdb'): return rpm
		
		# Compressed streams
		if string(0, '\037\213'):
			if self.path.endswith('.tar.gz') or self.path.endswith('.tgz'):
				return tgz
			return gz
		if string(0, 'BZh') or string(0, 'BZ'):
			if self.path.endswith('.tar.bz') or self.path.endswith('.tar.bz2') or \
			   self.path.endswith('.tbz') or self.path.endswith('.tbz2'):
				return tbz
			return bz2
		if string(0, 'begin '):
			return uue
		if string(0, '\037\235'):
			if self.path.endswith('.tar.Z'):
			    return tarz
			return z

		return make_gz

class DirData:
	mode = None
	def __init__(self, path):
		self.path = path
		self.default = make_tgz
		self.default_name = path + '.' + self.default.extension
