#
# Extensible User Folder
#
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
# ACN: 082 081 472  ABN: 83 082 081 472
# All Rights Reserved
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Author: Andrew Milton <akm@theinternet.com.au>
# $Id: UserCache.py,v 1.16 2003/07/10 21:11:26 akm Exp $

# Module Level Caches for Various User Operations

from time import time
from BTrees.OOBTree import OOBTree
import threading
from Acquisition import aq_inner
from Products.exUserFolder.User import User

class UserCacheItem:
	lastAccessed=0
	def __init__(self, username, password, cacheable):
		self.username=username
		self.password=password
		self.cacheable=cacheable
		self.lastAccessed=time()

	def touch(self):
		self.lastAccessed=time()
		
	def __repr__(self):
		return self.username

class NegativeUserCacheItem(UserCacheItem):
	def __init__(self, username):
		self.username=username
		self.lastAccessed=time()

class AdvancedCookieCacheItem(UserCacheItem):
	lastAccessed=0
	def __init__(self, username, password):
		self.username=username
		self.password=password
		self.lastAccessed=time()

class SessionExpiredException(Exception):
    'User Session Expired'


class UserCache:
	def __init__(self, sessionLength):
		self.sessionLength=sessionLength
		self.cache=OOBTree()
		self.hits=0
		self.fail=0
		self.nouser=0
		self.attempts=0
		self.timeouts=0
		self.cacheStarted=time()
		self.lock=threading.Lock()
		
	def addToCache(self, username, password, User):
		self.lock.acquire()		
		try:
			if not self.sessionLength:
				return

			try:
				u = self.cache.items(username)
				if u:
					for x in self.cache[username]:
						self.cache.remove(x)
			except:
				pass

			u = UserCacheItem(username, password, User._getCacheableDict())
			self.cache[username]=u
		finally:
			self.lock.release()

	def getUser(self, caller, username, password, checkpassword=1):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return None

			self.attempts=self.attempts+1

			u = None
			try:
				u = self.cache[username]
			except KeyError:
				self.nouser=self.nouser+1
				return None

			now = time()
			if u:
				if checkpassword and (u.password != password):
					self.fail=self.fail+1
					del self.cache[u.username]
				elif self.sessionLength and (
					(now - u.lastAccessed) > self.sessionLength):
					del self.cache[u.username]
					self.timeouts=self.timeouts+1
					user_object=User(u.cacheable,
									 caller.currentPropSource,
									 caller.cryptPassword,
									 caller.currentAuthSource,
									 caller.currentGroupSource)
					user_object.notifyCacheRemoval()
					del u
					raise SessionExpiredException
				else:
					u.touch()
					self.hits=self.hits+1
					return User(u.cacheable,
								caller.currentPropSource,
								caller.cryptPassword,
								caller.currentAuthSource,
								caller.currentGroupSource)

			self.nouser=self.nouser+1
			return None
		finally:
			self.lock.release()

	def removeUser(self, username):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return
			try:
				if self.cache[username]:
					del self.cache[username]
			except:
				pass
		finally:
			self.lock.release()

	def getCacheStats(self):
		self.lock.acquire()
		try:
			return (
				{'attempts':self.attempts,
				 'hits':self.hits,
				 'fail':self.fail,
				 'misses':self.nouser,
				 'cachesize':len(self.cache),
				 'time':self.cacheStarted,
				 'timeouts':self.timeouts,
				 'length':self.sessionLength})
		finally:
			self.lock.release()
		
	def getCurrentUsers(self, caller):
		self.lock.acquire()
		try:
			x=[]
			now = time()		
			for z in self.cache.keys():
				u = self.cache[z]
				if self.sessionLength and (
					(now - u.lastAccessed) > self.sessionLength):
					del self.cache[u.username]
					self.timeouts=self.timeouts+1
					user_object=User(u.cacheable,
									 caller.currentPropSource,
									 caller.cryptPassword,
									 caller.currentAuthSource,
									 caller.currentGroupSource)
					user_object.notifyCacheRemoval()
					del u
				else:
					x.append({'username':u.username,
							  'lastAccessed':u.lastAccessed})
			return x
		finally:
			self.lock.release()

class NegativeUserCache:
	def __init__(self, sessionLength):
		self.sessionLength=sessionLength
		self.cache=OOBTree()
		self.hits=0
		self.cacheStarted=time()
		self.lock=threading.Lock()
		
	def addToCache(self, username):
		self.lock.acquire()		
		try:
			if not self.sessionLength:
				return

			try:
				u = self.cache.items(username)
				if u:
					for x in self.cache[username]:
						self.cache.remove(x)
			except:
				pass

			u = NegativeUserCacheItem(username)
			self.cache[username]=u
		finally:
			self.lock.release()

	def getUser(self, username):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return 0

			u = None
			try:
				u = self.cache[username]
			except KeyError:
				return 0

			now = time()
			if u:
				if self.sessionLength and (
					(now - u.lastAccessed) > self.sessionLength):
					del self.cache[u.username]
				else:
					# We don't touch negative user caches
					# u.touch()
					self.hits=self.hits+1
					return 1
			return 0
		finally:
			self.lock.release()

	def removeUser(self, username):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return
			try:
				del self.cache[username]
			except:
				pass
		finally:
			self.lock.release()					

class CookieCache:
	def __init__(self, sessionLength):
		self.sessionLength=sessionLength
		self.cache=OOBTree()
		self.hits=0
		self.cacheStarted=time()
		self.lock=threading.Lock()
		
	def addToCache(self, username, password, key):
		self.lock.acquire()		
		try:
			if not self.sessionLength:
				return

			try:
				u = self.cache.items(key)
				if u:
					for x in self.cache[key]:
						self.cache.remove(x)
			except:
				pass
			u = AdvancedCookieCacheItem(username, password)
			self.cache[key]=u
		finally:
			self.lock.release()

	def getUser(self, key):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return None

			u = None
			try:
				u = self.cache[key]
			except KeyError:
				return None

			now = time()
			if u:
				if self.sessionLength and (
					(now - u.lastAccessed) > self.sessionLength):
					del self.cache[key]
				else:
					# We don't touch negative user caches
					# u.touch()
					self.hits=self.hits+1
					return u.username, u.password
			return None
		finally:
			self.lock.release()

	def removeUser(self, key):
		self.lock.acquire()
		try:
			if not self.sessionLength:
				return
			try:
				del self.cache[key]
			except:
				pass
		finally:
			self.lock.release()

class GlobalUserCache:
	caches={}
	def __init__(self):
		self.lock = threading.Lock()

	def createCache(self, who, sessionLength):
		self.lock.acquire()
		try:
			self.caches[who]=UserCache(sessionLength)
			return self.caches[who]
		finally:
			self.lock.release()

	def getCache(self, who):
		self.lock.acquire()
		try:
			if self.caches.has_key(who):
				return self.caches[who]
			else:
				return None
		finally:
			self.lock.release()
			
	def deleteCache(self, who):
		self.lock.acquire()
		try:
			del self.caches[who]
		finally:
			self.lock.release()

class GlobalNegativeUserCache:
	caches={}
	def __init__(self):
		self.lock = threading.Lock()

	def createCache(self, who, sessionLength):
		self.lock.acquire()
		try:
			self.caches[who]=NegativeUserCache(sessionLength)
			return self.caches[who]
		finally:
			self.lock.release()

	def getCache(self, who):
		self.lock.acquire()
		try:
			if self.caches.has_key(who):
				return self.caches[who]
			else:
				return None
		finally:
			self.lock.release()
			
	def deleteCache(self, who):
		self.lock.acquire()
		try:
			del self.caches[who]
		finally:
			self.lock.release()

class GlobalAdvancedCookieCache:
	caches={}
	def __init__(self):
		self.lock = threading.Lock()

	def createCache(self, who, sessionLength):
		self.lock.acquire()
		try:
			self.caches[who]=CookieCache(sessionLength)
			return self.caches[who]
		finally:
			self.lock.release()

	def getCache(self, who):
		self.lock.acquire()
		try:
			if self.caches.has_key(who):
				return self.caches[who]
			else:
				return None
		finally:
			self.lock.release()
			
	def deleteCache(self, who):
		self.lock.acquire()
		try:
			del self.caches[who]
		finally:
			self.lock.release()