# relay.py: PyLink Relay plugin
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pickle
import sched
import threading
import time

import utils
from log import log

dbname = "pylinkrelay.db"

def loadDB():
    global db
    try:
        with open(dbname, "rb") as f:
            db = pickle.load(f)
    except (ValueError, IOError):
        log.exception("Relay: failed to load links database %s"
            ", creating a new one in memory...", dbname)
        db = {}

def exportDB(scheduler):
    scheduler.enter(60, 1, exportDB, argument=(scheduler,))
    log.debug("Relay: exporting links database to %s", dbname)
    with open(dbname, 'wb') as f:
        pickle.dump(db, f, protocol=4)

@utils.add_cmd
def create(irc, source, args):
    """<channel>

    Creates the channel <channel> over the relay."""
    try:
        channel = args[0].lower()
    except IndexError:
        utils.msg(irc, source, "Error: not enough arguments. Needs 1: channel.")
        return
    if not utils.isChannel(channel):
        utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
        return
    if source not in irc.channels[channel]:
        utils.msg(irc, source, 'Error: you must be in %r to complete this operation.' % channel)
        return
    if not utils.isOper(irc, source):
        utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.' % channel)
        return
    db[(irc.name, channel)] = {'claim': [irc.name], 'links': [], 'blocked_nets': []}
    irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
    utils.msg(irc, source, 'Done.')

@utils.add_cmd
def destroy(irc, source, args):
    """<channel>

    Destroys the channel <channel> over the relay."""
    try:
        channel = args[0].lower()
    except IndexError:
        utils.msg(irc, source, "Error: not enough arguments. Needs 1: channel.")
        return
    if not utils.isChannel(channel):
        utils.msg(irc, source, 'Error: invalid channel %r.' % channel)
        return
    if not utils.isOper(irc, source):
        utils.msg(irc, source, 'Error: you must be opered in order to complete this operation.' % channel)
        return

    if channel in db:
        del db[channel]
        if channel not in map(str.lower, irc.serverdata['channels']):
            irc.proto.partClient(irc, irc.pseudoclient.uid, channel)
        utils.msg(irc, source, 'Done.')
    else:
        utils.msg(irc, source, 'Error: no such relay %r exists.' % channel)

def main(irc):

    loadDB()
    # HACK: we only want to schedule this once globally, because
    # exportDB will otherwise be called by every network that loads this
    # plugin.
    if 'relaydb' not in utils.schedulers:
        utils.schedulers['relaydb'] = scheduler = sched.scheduler()
        scheduler.enter(30, 1, exportDB, argument=(scheduler,))
        # Thread this because exportDB() queues itself as part of its
        # execution, in order to get a repeating loop.
        thread = threading.Thread(target=scheduler.run)
        thread.start()
    for chanpair in db:
        network, channel = chanpair
        ircobj = utils.networkobjects[network]
        ircobj.proto.joinClient(ircobj, irc.pseudoclient.uid, channel)
    for network, ircobj in utils.networkobjects.items():
        if ircobj.name != irc.name:
            irc.proto.spawnServer(irc, '%s.relay' % network)

@@ -241,16 +241,1367 @@
 ay.db%22%0A%0A
+def normalizeNick(irc, nick, separator=%22/%22):%0A    orig_nick = nick%0A    protoname = irc.proto.__name__%0A    maxnicklen = irc.maxnicklen%0A    netname = irc.name%0A    if protoname == 'charybdis':%0A        # Charybdis doesn't allow / in usernames, and will quit with%0A        # a protocol violation if there is one.%0A        separator = separator.replace('/', '%7C')%0A        nick = nick.replace('/', '%7C')%0A    tagnicks = True%0A%0A    suffix = separator + netname%0A    nick = nick%5B:maxnicklen%5D%0A    # Maximum allowed length of a nickname.%0A    allowedlength = maxnicklen - len(suffix)%0A    # If a nick is too long, the real nick portion must be cut off, but the%0A    # /network suffix must remain the same.%0A%0A    nick = nick%5B:allowedlength%5D%0A    nick += suffix%0A    while utils.nickToUid(irc, nick):%0A        # The nick we want exists? Darn, create another one then.%0A        # Increase the separator length by 1 if the user was already tagged,%0A        # but couldn't be created due to a nick conflict.%0A        # This can happen when someone steals a relay user's nick.%0A        new_sep = separator + separator%5B-1%5D%0A        nick = normalizeNick(irc, orig_nick, separator=new_sep)%0A    finalLength = len(nick)%0A    assert finalLength %3C= maxnicklen, %22Normalized nick %25r went over max %22 %5C%0A        %22nick length (got: %25s, allowed: %25s!%22 %25 (nick, finalLength, maxnicklen)%0A%0A    return nick%0A%0A
 def load

add normalizeNick stub