import os
from idiokit import jid
from abusehelper.core import serialize, config

class Pipeable(object):
    def _collect(self):
        return self

    def __or__(self, other):
        if not isinstance(other, Pipeable):
            raise TypeError("%r is not pipeable" % other)
        return Pipe(self, other)

class PipeError(Exception):
    pass

class Pipe(Pipeable):
    def __init__(self, *pieces):
        self.pieces = pieces
    
    def _collect(self, pieces=None):
        for piece in self.pieces:
            yield piece._collect()

    def __iter__(self):
        prev = None

        for piece in config.flatten(self._collect()):
            if prev is not None:
                if isinstance(prev, Room) and isinstance(piece, Session):
                    piece = piece.updated(src_room=prev.name)
                elif isinstance(prev, Session) and isinstance(piece, Room):
                    yield prev.updated(dst_room=piece.name)
                elif isinstance(prev, Room) and isinstance(piece, Room):
                    yield Session("roomgraph", 
                                  src_room=prev.name,
                                  dst_room=piece.name)
                elif isinstance(piece, Session):
                    raise PipeError("a Session instance has to be piped "+
                                    "directly after a Room instance")
            prev = piece

        if isinstance(prev, Session):
            yield prev

class SessionError(Exception):
    pass

class Session(Pipeable):
    @property
    def conf(self):
        return dict(self._conf)

    def __init__(self, service, *path, **conf):
        self.__dict__["service"] = service
        self.__dict__["path"] = tuple(path)
        
        for key, value in conf.items():
            try:
                value  = serialize.load(serialize.dump(value))
            except serialize.UnregisteredType:
                raise SessionError("can not serialize key %r value %r" % 
                                   (key, value))
            conf[key] = value
        self.__dict__["_conf"] = frozenset(conf.items())
        self.__dict__["_hash"] = None

    def updated(self, **conf):
        new_conf = dict(self._conf)
        new_conf.update(conf)
        return Session(self.service, *self.path, **new_conf)

    def __setitem__(self, key, value):
        raise AttributeError("%r instances are immutable" % self.__class__)

    def __delitem__(self, key):
        raise AttributeError("%r instances are immutable" % self.__class__)

    def __hash__(self):
        if self._hash is None:
            self._hash = hash(self.service) ^ hash(self._conf) ^ hash(self.path)
        return self._hash

    def __eq__(self, other):
        if not isinstance(other, Session):
            return NotImplemented
        if self.service != other.service:
            return False
        if self.path != self.path:
            return False
        return self._conf == other._conf

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return result
        return not result

class Room(Pipeable):
    def __init__(self, name):
        name = unicode(name)
        try:
            jid.nodeprep(name)
        except jid.JIDError:
            jid.JID(name)
        self.name = name

from idiokit import threado, timer
from abusehelper.core import bot, services, log

class Cancel(Exception):
    pass

class RuntimeBot(bot.XMPPBot):
    service_room = bot.Param()

    @threado.stream
    def configs(inner, self):
        while True:
            yield inner

    @threado.stream
    def _handle_updates(inner, self, lobby):
        sessions = dict()

        try:
            while True:
                configs = yield inner

                added = set(config.flatten(configs))

                for key in set(sessions) - added:
                    stream = sessions.pop(key)
                    stream.throw(Cancel())

                for session in added - set(sessions):
                    sessions[session] = self.session(lobby, session)
        finally:
            for stream in sessions.values():
                stream.throw(Cancel())        

    @threado.stream
    def main(inner, self):
        xmpp = yield inner.sub(self.xmpp_connect())

        self.log.info("Joining lobby %r", self.service_room)
        lobby = yield inner.sub(services.join_lobby(xmpp, 
                                                    self.service_room,
                                                    self.bot_name))
        self.log.addHandler(log.RoomHandler(lobby.room))
        yield inner.sub(self.configs() | self._handle_updates(lobby) | lobby)

    @threado.stream
    def session(inner, self, lobby, session):
        name = session.service
        if session.path:
            name += "(" + ".".join(session.path) + ")"
        
        while True:
            self.log.info("Waiting for %r", name)
            try:
                stream = yield inner.sub(lobby.session(session.service, 
                                                       *session.path, 
                                                       **session.conf))
            except Cancel:
                self.log.info("Stopped waiting for %r", name)
                break

            conf_str = "\n".join(" %r=%r" % item for item 
                                 in session.conf.items())
            self.log.info("Sent %r conf:\n%s", name, conf_str)
                
            try:
                yield inner.sub(stream)
            except services.Stop:
                self.log.info("Lost connection to %r", name)
            except Cancel:
                self.log.info("Ended connection to %r", name)
                break

class DefaultRuntimeBot(RuntimeBot):
    config = bot.Param("configuration module")
    poll_interval = bot.IntParam("how often (in seconds) the "+
                                 "configuration module is checked "+
                                 "for updates (default: %default)",
                                 default=1)

    def _flatten_runtime_methods(self, configs):
        # Backwards compatibility
        for obj in configs:
            runtime = getattr(obj, "runtime", None)
            if callable(runtime):
                obj = runtime()
            yield obj

    @threado.stream
    def configs(inner, self):
        conf_path = os.path.abspath(self.config)
        last_mtime = None
        error_msg = None

        while True:
            try:
                mtime = os.path.getmtime(conf_path)
                if last_mtime != mtime:
                    last_mtime = mtime

                    configs = config.load_configs(conf_path)
                    configs = list(self._flatten_runtime_methods(configs))
                    inner.send(configs)

                    error_msg = None
            except BaseException, exception:
                if error_msg != str(exception):
                    error_msg = str(exception)
                    self.log.error("Couldn't load module %r: %s", 
                                   self.config, error_msg)

            yield inner, timer.sleep(self.poll_interval)

if __name__ == "__main__":
    DefaultRuntimeBot.from_command_line().execute()

@@ -5852,253 +5852,8 @@
 le%22)
-%0A    poll_interval = bot.IntParam(%22how often (in seconds) the %22+%0A                                 %22configuration module is checked %22+%0A                                 %22for updates (default: %25default)%22,%0A                                 default=1)
 %0A%0A  
@@ -6205,228 +6205,8 @@
 ig)%0A
-        last_mtime = None%0A        error_msg = None%0A%0A        while True:%0A            try:%0A                mtime = os.path.getmtime(conf_path)%0A                if last_mtime != mtime:%0A                    last_mtime = mtime%0A
 %0A   
@@ -6202,32 +6202,37 @@
 onfig)%0A%0A        
+try:%0A
             conf
@@ -6276,32 +6276,24 @@
             
- 
-       
 configs = li
@@ -6347,401 +6347,203 @@
     
-            inner.send(configs)%0A%0A                    error_msg = None%0A            except BaseException, exception:%0A                if error_msg != str(exception):%0A                    error_msg = str(exception)%0A                    self.log.error(%22Couldn't load module %25r: %25s%22, %0A                                   self.config, error_msg)%0A%0A            yield inner, timer.sleep(self.poll_interval)
+except BaseException, exc:%0A            self.log.error(%22Couldn't load module %25r: %25s%22, self.config, exc)%0A            return%0A%0A        inner.send(configs)%0A%0A        while True:%0A            yield inner
 %0A%0Aif

Remove support for constantly polling the configuration module.