#!/usr/bin/python

import fuse
from fuse import Fuse

from time import time

import stat	# for file properties
import os	  # for filesystem modes (O_RDONLY, etc)
import errno   # for error number codes (ENOENT, etc)
			   # - note: these must be returned as negatives

import sipb_xen_database

fuse.fuse_python_api = (0, 2)

realpath = "/home/machines/"

def dirFromList(list):
	"""
	Return a properly formatted list of items suitable to a directory listing.
	['a', 'b', 'c'] => [('a', 0), ('b', 0), ('c', 0)]
	"""
	return [(x, 0) for x in list]

def getDepth(path):
	"""
	Return the depth of a given path, zero-based from root ('/')
	"""
	if path == '/':
		return 0
	else:
		return path.count('/')

def getParts(path):
	"""
	Return the slash-separated parts of a given path as a list
	"""
	if path == '/':
		return ['/']
	else:
		return path[1:].split('/')

class MyStat:
	def __init__(self):
		self.st_mode = 0
		self.st_ino = 0
		self.st_dev = 0
		self.st_nlink = 0
		self.st_uid = 0
		self.st_gid = 0
		self.st_size = 0
		self.st_atime = 0
		self.st_mtime = 0
		self.st_ctime = 0
	
	def toTuple(self):
		return (self.st_mode, self.st_ino, self.st_dev, self.st_nlink, self.st_uid, self.st_gid, self.st_size, self.st_atime, self.st_mtime, self.st_ctime)

class ConsoleFS(Fuse):
	"""
	"""
	
	def __init__(self, *args, **kw):
		Fuse.__init__(self, *args, **kw)
		self.lasttime = time()
		self.allow_other = 1
		print 'Init complete.'
	
	def mirrorPath(self, path):
		return realpath + "/".join(getParts(path)[1:])
	
	def getMachines(self):
		if time() - self.lasttime > 15:
			self.lasttime = time()
			sipb_xen_database.clear_cache()
		return [machine.name for machine in sipb_xen_database.Machine.select()]
	
	def getUid(self, machine_name):
		return sipb_xen_database.Machine.get_by(name=machine_name).machine_id + 1000
	
	def getK5login(self, machine_name):
		machine = sipb_xen_database.Machine.get_by(name=machine_name)
		users = [acl.user for acl in machine.acl]
		return "\n".join(map(self.userToPrinc, users) + [''])
	
	def userToPrinc(self, user):
		if '@' in user:
			(princ, realm) = user.split('@')
		else:
			princ = user
			realm = "ATHENA.MIT.EDU"
		
		return princ.replace('.', '/') + realm
	
	def getattr(self, path):
		"""
		- st_mode (protection bits)
		- st_ino (inode number)
		- st_dev (device)
		- st_nlink (number of hard links)
		- st_uid (user ID of owner)
		- st_gid (group ID of owner)
		- st_size (size of file, in bytes)
		- st_atime (time of most recent access)
		- st_mtime (time of most recent content modification)
		- st_ctime (platform dependent; time of most recent metadata change on Unix,
					or the time of creation on Windows).
		"""
		
		print "*** getattr: " + path
		
		depth = getDepth(path)
		parts = getParts(path)
		
		st = MyStat()
		if path == '/':
			st.st_mode = stat.S_IFDIR | 0755
			st.st_nlink = 2
		elif depth == 1:
			if parts[-1] in self.getMachines():
				st.st_mode = stat.S_IFDIR | 0755
				st.st_nlink = 2
				st.st_uid = st.st_gid = self.getUid(parts[0])
			else:
				return -errno.ENOENT
		elif depth == 2 and parts[-1] == '.k5login':
			st.st_mode = stat.S_IFREG | 0444
			st.st_nlink = 1
			st.st_size = len(self.getK5login(parts[0]))
			st.st_uid = st.st_gid = self.getUid(parts[0])
		else:
			stats = list(os.lstat(self.mirrorPath(path)))
			stats[4:6] = [self.getUid(parts[0])] * 2
			return tuple(stats)
		return st.toTuple()
	
	def readdir(self, path, offset):
		print '*** readdir', path, offset
		for (value, zero) in self.getdir(path):
			yield fuse.Direntry(value)
	
	def getdir(self, path):
		print '*** getdir', path
		if path == '/':
			contents = ['.', '..']+self.getMachines()
		elif getDepth(path) == 1:
			contents = set(os.listdir(self.mirrorPath(path)) + ['.k5login', '.', '..'])
		else:
			contents = os.listdir(self.mirrorPath(path)) + ['.', '..']
		return [(i, 0) for i in contents]
	
	def read ( self, path, length, offset ):
		print '*** read', path, length, offset
		
		parts = getParts(path)
		
		if getDepth(path) < 2:
			return -errno.ENOENT
		elif parts[1:] == ['.k5login']:
			if parts[0] not in self.getMachines():
				return -errno.ENOENT
			else:
				return self.getK5login(parts[0])[offset:length + offset]
		else:
			fname = self.mirrorPath(path)
			if not os.path.isfile(fname):
				return -errno.ENOENT
			else:
				f = open(fname)
				f.seek(offset)
				return f.read(length)

if __name__ == '__main__':
	sipb_xen_database.connect('postgres://sipb-xen@sipb-xen-dev.mit.edu/sipb_xen')
	usage="""
ConsoleFS [mount_path]
"""
	server = ConsoleFS()
	server.flags = 0
	server.main()
