
import sys
import weakref

import gio

def load_module(path):
    source = file(path).read()
    code = compile(source, path, 'exec')
    globals_dict = {}
    exec code in globals_dict
    return globals_dict

class Reload(object):
    """Updates instances when class source code changes."""

    _reload_monitors = {}
    _reload_instances = {}
    _rate_limit = None

    def __init__(self):
        class_name = self.__class__.__name__
        module_name = self.__module__
        #print class_name

        if class_name not in self._reload_monitors:
            module_path = sys.modules[module_name].__file__
            self._reload_monitors[class_name] = \
                self._create_monitor(module_path)

        instances = self._reload_instances.setdefault(
            class_name, weakref.WeakKeyDictionary())
        instances[self] = True

    def _create_monitor(self, path):
        monitor = gio.File(path).monitor()
        monitor.connect('changed', self._file_changed)
        return monitor

    def _update_instance(self, cls, instance):
        instance.__class__ = cls

    def _update_class(self, class_name, cls):
        for instance in self._reload_instances[class_name].keys():
            self._update_instance(cls, instance)

    def _file_changed(self, monitor, file, _other_file, event_type):
        if event_type != gio.FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
            return

        self._reload()

    def _reload(self):
        class_name = self.__class__.__name__
        module_name = self.__module__
        module_path = sys.modules[module_name].__file__

        try:
            globals_dict = load_module(module_path)
        except Exception, e:
            # XXX: Should use a logger for this.
            print repr(e)
            return

        cls = globals_dict.get(class_name)

        if cls is None:
            # The class we're trying to reload is no longer defined in
            # the file that it originally came from. Not much we can
            # do about that.
            raise Warning('Class missing on reload',
                file.get_path(), class_name)
            return

        # Setting this means that repr() gives consistent results.
        cls.__module__ = module_name

        self._update_class(class_name, cls)

        # It seems that if this file gets loaded, this class gets
        # re-evaluated and hence the variables get reset. Since we
        # don't handle this gracefully, check it doesn't happen.
        assert self._reload_instances
