#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Toonloop 
#
# Copyright 2010 Alexandre Quessy
# <alexandre@quessy.net>
# http://www.toonloop.com
#
# Toonloop 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 3 of the License, or
# (at your option) any later version.
#
# Toonloop 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 Toonloop.  If not, see <http://www.gnu.org/licenses/>.
#
"""
Tools to watch changes in a directory on GNU/Linux.

Needs python-pyinotify

"""
import pyinotify
from twisted.internet import reactor
from twisted.internet import task

__version__ = "0.1.0"

def call_each(callbacks, *args, **kwargs):
    for callback in callbacks:
        callback(*args, **kwargs)

class _FileSystemEventsHandler(pyinotify.ProcessEvent):
    """
    Inherits from ProcessEvent (the processing base class), it will handle notifications and take actions through specifics processing methods whose the name follows this convention: process_EVENT_TYPE where EVENT_TYPE is the type of event to process (see EventsCodes. 
    """
    def process_IN_CREATE(self, event):
        call_each(self.created_signal, event.pathname)
    
    def process_IN_MOVED_TO(self, event):
        call_each(self.moved_to_signal, event.pathname)

    def process_IN_DELETE(self, event):
        call_each(self.deleted_signal, event.pathname)

    def process_IN_CLOSE_WRITE(self, event):
        call_each(self.close_write_signal, event.pathname)

class DirectoryWatcher(object):
    """
    created_signal and deleted signals are list of callbacks. 
    """
    def __init__(self, directory=".", notifier_timeout=10):
        self.notifier_timeout = notifier_timeout
        self.directory = directory

        # The watch manager stores the watches and provides operations on watches
        self._watch_manager = pyinotify.WatchManager()
        self._events_handler = _FileSystemEventsHandler()
        self.deleted_signal = []
        self.created_signal = []
        self.moved_to_signal = []
        self.close_write_signal = []

        self._events_handler.deleted_signal = self.deleted_signal
        self._events_handler.created_signal = self.created_signal
        self._events_handler.moved_to_signal = self.moved_to_signal
        self._events_handler.close_write_signal = self.close_write_signal

        self._notifier = pyinotify.Notifier(self._watch_manager, self._events_handler, timeout=self.notifier_timeout)
        mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO | pyinotify.IN_CLOSE_WRITE # watched events
        self._path_watcher = self._watch_manager.add_watch(self.directory, mask, rec=True)

    def poll(self):
        """
        Quickly checks for file system events.
        """
        assert self._notifier._timeout is not None, 'Notifier must be constructed with a short timeout'
        self._notifier.process_events()
        while self._notifier.check_events():  #loop in case more events appear while we are processing
            self._notifier.read_events()
            self._notifier.process_events()

    def __del__(self):
        self._notifier.stop()

def run(directory="/tmp"):
    def on_path_created(path):
        print path, 'created'
    def on_path_deleted(path):
        print path, 'deleted'
    def on_path_moved_to(path):
        print path, 'moved'
    def on_close_write(path):
        print path, 'done written'
    
    watcher = DirectoryWatcher(directory)
    watcher.created_signal.append(on_path_created)
    watcher.deleted_signal.append(on_path_deleted)
    watcher.moved_to_signal.append(on_path_moved_to)
    watcher.close_write_signal.append(on_close_write)
    
    looping_call = task.LoopingCall(watcher.poll)
    looping_call.start(0.1, now=False)

if __name__ == "__main__":
    import optparse
    parser = optparse.OptionParser(usage="%prog", version=__version__) 
    parser.add_option("-d", "--directory", type="string", help="Specifies the directory in which to look for files altered.", default='.')
    (options, args) = parser.parse_args()
    run(options.directory)
    try:
        reactor.run()
    except KeyboardInterrupt:
        pass


