import os
import errno

from parallels.core.utils.common import unused
from parallels.core.utils.common import is_run_on_windows


class FileLockException(Exception):
	pass


class FileLock(object):
	"""
	Provide ability to synchronize several processes of Panel Migrator

	Basic idea is:
	1. then first process aguire lock, special file (lock file) will be created
	2. then second process try to aquire lock, it try to remove early created file,
	but lock file descriptor is in use by first process, so FileLockException will be raised
	3. than first process was finished (or killed), lock file descriptor will be closed,
	so other processes would be able to aquire lock
	"""

	def __init__(self, lock_path):
		self.is_locked = False
		self.lock_path = lock_path
		self.lock_file_descriptor = None

	def acquire(self):
		self._acquire_windows() if is_run_on_windows() else self._acquire_unix()

	def release(self):
		self._release_windows() if is_run_on_windows() else self._release_unix()

	def _acquire_unix(self):
		import fcntl
		try:
			self.lock_file_descriptor = os.open(self.lock_path, os.O_CREAT | os.O_RDWR)
			fcntl.lockf(self.lock_file_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
		except IOError as e:
			if e.errno != errno.EACCES and e.errno != errno.EAGAIN:
				raise
			raise FileLockException()
		self.is_locked = True

	def _acquire_windows(self):
		try:
			if os.path.exists(self.lock_path):
				os.unlink(self.lock_path)
			self.lock_file_descriptor = os.open(self.lock_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
		except OSError as e:
			if e.errno != errno.EEXIST and e.errno != errno.EACCES:
				raise
			raise FileLockException()
		self.is_locked = True

	def _release_unix(self):
		import fcntl
		if self.is_locked:
			fcntl.lockf(self.lock_file_descriptor, fcntl.LOCK_UN)
			os.close(self.lock_file_descriptor)
			self.is_locked = False

	def _release_windows(self):
		if self.is_locked:
			os.close(self.lock_file_descriptor)
			os.unlink(self.lock_path)
			self.is_locked = False

	def __enter__(self):
		if not self.is_locked:
			self.acquire()
		return self

	def __exit__(self, exception_type, exception_value, traceback):
		unused(exception_type, exception_value, traceback)
		self.release()

	def __del__(self):
		self.release()