##############################################################################
#
# Copyright (c) 2011 Nexedi SARL and Contributors. All Rights Reserved.
#                    Julien Muchembled <jm@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import errno, glob, os, threading
from Acquisition import aq_base
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type.TransactionalVariable import TransactionalResource
from Products.ERP5.mixin.timer_service import TimerServiceMixin
from AccessControl.SecurityManagement import newSecurityManager, \
  getSecurityManager, setSecurityManager
import six

# TODO: Current API was designed to avoid compability issues in case it is
#       reimplemented using https://pypi.python.org/pypi/pyinotify

IN_CREATE = 1
IN_MODIFY = 2
IN_DELETE = 512

timerservice_lock = threading.Lock()
inotify_state_dict = {}

class InotifyTool(TimerServiceMixin, BaseTool):
  """
  """

  id = 'portal_inotify'
  meta_type = 'ERP5 Inotify Tool'
  portal_type = 'Inotify Tool'
  title = 'Inotifies'

  def resetCache(self):
    self._p_changed = 1
    try:
      del self._v_notify_list
    except AttributeError:
      pass

  def process_timer(self, tick, interval, prev="", next=""): # pylint: disable=redefined-builtin
    if timerservice_lock.acquire(0):
      try:
        try:
          notify_list = aq_base(self)._v_notify_list
        except AttributeError:
          current_node = self.getCurrentNode()
          self._v_notify_list = notify_list = [x.getId()
            for x in self.objectValues()
            if x.isEnabled() and current_node in x.getNodeList()]
        update_state_dict = {}
        original_security_manager = getSecurityManager()
        for notify_id in notify_list:
          notify = self._getOb(notify_id)
          newSecurityManager(None, notify.getWrappedOwner())
          try:
            inode_path = notify.getInodePath()
            if inode_path:
              path = notify.getPath()
              state = inotify_state_dict.get(path, {})
              new_state = {}
              for inode_path in glob.glob(inode_path):
                for name in os.listdir(inode_path):
                  p = os.path.join(inode_path, name)
                  try:
                    s = os.lstat(p)
                  except OSError as e:
                    if e.errno != errno.ENOENT:
                      raise
                  else:
                    new_state[p] = s.st_mtime, s.st_size
              if new_state != state:
                update_state_dict[path] = new_state
                events = [{'path': p, 'mask': IN_DELETE}
                  for p in set(state).difference(new_state)]
                for p, m in six.iteritems(new_state):
                  if p in state:
                    if m == state[p]:
                      continue
                    mask = IN_MODIFY
                  else:
                    mask = IN_CREATE
                  events.append({'path': p, 'mask': mask})
                getattr(notify, notify.getSenseMethodId())(events)
          finally:
            setSecurityManager(original_security_manager)

        if update_state_dict:
          TransactionalResource(tpc_finish=lambda txn:
            inotify_state_dict.update(update_state_dict))
      finally:
        timerservice_lock.release()