
# Copyright (c) 2005 Antoon Pardon
# 
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

from threading import Lock
from thread import get_ident

import os

from types import BooleanType

class EOInformation(Exception):
  pass

class TubeAccess(Exception):
  pass

class Fifo:

  def __init__(self):
    self.fifo = []

  def put(self, item):
    self.fifo.append(item)

  def get(self):
    return self.fifo.pop(0)

  def size(self):
    return len(self.fifo)

class Tube:

  def __init__(self, maxsize, lck = Lock, container = None):
    if container is None:
      container = Fifo()
    self.readers = set()
    self.writers = set()
    self.container = container
    self.maxsize = maxsize
    self.rd, self.wr = os.pipe()
    self.in_use = Lock()
    self.nowriter = lck()
    self.full = lck()
    self.empty = lck()
    self.empty.acquire()
    self.nowriter.acquire()

  def __del__(self):
    os.close(self.rd)
    os.close(self.wr)

  def open(self, access = 'r', *to):
    thrd = get_ident()
    access = access.lower()
    self.in_use.acquire()
    if 'w' in access:
      if len(self.writers) == 0:
        for _ in self.readers:
	  self.nowriter.release()
      self.writers.add(thrd)
    if 'r' in access:
      self.readers.add(thrd)
      if len(self.writers) == 0:
        self.in_use.release()
	self.nowriter.acquire(*to)
      else:
        self.in_use.release()
    else:
      self.in_use.release()

  def close(self, access = 'rw'):
    thrd = get_ident()
    access = access.lower()
    self.in_use.acquire()
    if 'r' in access:
      self.readers.discard(thrd)
    if 'w' in access:
      self.writers.discard(thrd)
      if len(self.writers) == 0:
	if self.container.size() == 0:
	  self.empty.release()
	for _ in self.readers:
	  self.container.put(EOInformation)
	  os.write(self.wr, '*')
    self.in_use.release()

  def size(self):
    self.in_use.acquire()
    size = self.container.size()
    self.in_use.release()
    return size

  def get(self, *to):
    thrd = get_ident()
    if thrd not in self.readers:
      raise TubeAccess, "Thread has no read access for tube"
    self.empty.acquire(*to)
    self.in_use.acquire()
    size = self.container.size()
    if size == self.maxsize:
      self.full.release()
    item = self.container.get()
    os.read(self.rd, 1)
    if size != 1:
      self.empty.release()
    self.in_use.release()
    if item is EOInformation:
      raise EOInformation
    else:
      return item

  def put(self, item, *to):
    thrd = get_ident()
    if thrd not in self.writers:
      raise TubeAccess, "Thread has no write access for tube"
    if thrd in self.readers:
      self._put_rw(item)
    else:
      self._put_wo(item, *to)

  def _put_wo(self, item, *to):
    self.full.acquire(*to)
    self.in_use.acquire()
    size = self.container.size()
    if size == 0:
      self.empty.release()
    self.container.put(item)
    os.write(self.wr, '*')
    if size + 1 < self.maxsize:
      self.full.release()
    self.in_use.release()

  def _put_rw(self, item):
    self.in_use.acquire()
    size = self.container.size()
    if size == 0:
      self.empty.release()
    self.container.put(item)
    os.write(self.wr, '*')
    self.in_use.release()


def _add_io_callback(fd, callback, *args):

  def io_callback(fd, cb_cnd, *args):

    return callback(*args)
  #end io_callback

  return gob.io_add_watch(fd, gob.IO_IN, io_callback, *args)
#end add_io_callback    

def tube_add_watch(tube, callback, *args):

  import gobject as gob

  def io_callback(fd, cb_cnd, *args):

    return callback(*args)
  #end io_callback

  return gob.io_add_watch(tube.rd, gob.IO_IN, io_callback, tube, *args)
