mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 21:29:24 +01:00
50c650ace3
Allows multiple configurable queues on multiple channels. -- Azaroth
597 lines
22 KiB
Python
597 lines
22 KiB
Python
#!/usr/bin/env python
|
|
|
|
import plugins
|
|
|
|
import socket
|
|
import textwrap
|
|
import threading
|
|
import struct
|
|
import time
|
|
import os
|
|
import os.path
|
|
import glob
|
|
|
|
import dcc
|
|
import conf
|
|
import utils
|
|
import world
|
|
import ircmsgs
|
|
import ircutils
|
|
import privmsgs
|
|
import callbacks
|
|
import registry
|
|
|
|
conf.registerPlugin('FServe')
|
|
conf.registerGlobalValue(conf.supybot.plugins.FServe, 'packetSize',
|
|
registry.Integer(1024, 'Size of packets to send/receive with DCC'))
|
|
conf.registerGroup(conf.supybot.plugins.FServe, 'queues')
|
|
conf.registerChannelValue(conf.supybot.plugins.FServe, 'queueNames',
|
|
registry.SpaceSeparatedListOfStrings([],
|
|
'List of queues for this channel'))
|
|
|
|
# --- Exceptions ---
|
|
|
|
class QueueException(Exception):
|
|
msg = "Could not queue file"
|
|
def __init__(self, m):
|
|
self.msg = m
|
|
|
|
|
|
# --- Handlers ---
|
|
|
|
class SendHandler(dcc.SendHandler):
|
|
caller = None
|
|
|
|
def clientConnected(self):
|
|
self.sock.settimeout(self.caller.timeout)
|
|
|
|
def packetSent(self):
|
|
self.currentSpeed = (self.bytesSent / (time.time() - self.startTime))
|
|
if (self.currentSpeed > self.caller.maxSpeed):
|
|
time.sleep(0.9)
|
|
if (self.currentSpeed < self.caller.minSpeed):
|
|
#Allow a little leeway
|
|
slow += 1
|
|
if (slow > 5):
|
|
# Too Slow
|
|
self.log.info('\'%s\': Send too slow' % (self.filename))
|
|
raise SendException('Too slow')
|
|
|
|
def clientClosed(self):
|
|
# alert queue that we're done
|
|
self.caller.sendFinished(self)
|
|
|
|
def registerPort(self):
|
|
pass
|
|
|
|
|
|
|
|
class ChatHandler(dcc.ChatHandler):
|
|
|
|
def __init__(self, irc, nick, mask, queue=None):
|
|
self.caller = queue
|
|
dcc.ChatHandler.__init__(self, irc, nick, mask)
|
|
if (queue):
|
|
self.currentDir = os.path.join(conf.supybot.directories.data(),
|
|
queue.sendDir)
|
|
else:
|
|
self.currentDir = os.path.join(conf.supybot.directories.data())
|
|
self.baseDir = self.currentDir
|
|
|
|
def cmd_ls(self, args):
|
|
""" Show files in current directory, using globs """
|
|
if args:
|
|
files = []
|
|
for a in args:
|
|
for i in glob.glob(os.path.join(self.currentDir, a)):
|
|
if (i[:2] == "./"):
|
|
i = i[2:]
|
|
if (i[:len(self.currentDir)] == self.currentDir):
|
|
i = i[len(self.currentDir)+1:]
|
|
if (not i in files):
|
|
files.append(i)
|
|
else:
|
|
files = os.listdir(self.currentDir)
|
|
if (not files):
|
|
self.sock.send("There are no matching files.\n");
|
|
files.sort()
|
|
flist = []
|
|
dirlist = []
|
|
for f in files:
|
|
full = os.path.join(self.currentDir, f)
|
|
if (os.path.isdir(full)):
|
|
dirlist.append(ircutils.mircColor(' %s/' % f, 'blue'))
|
|
else:
|
|
size = os.path.getsize(full)
|
|
if (size > 1024000):
|
|
size = '%sMb' % (size / 1024000)
|
|
elif (size > 1024):
|
|
size = '%sKb' % (size / 1024)
|
|
flist.append('%s [%s]' % (f, size))
|
|
self.sock.send('\n'.join(dirlist) + '\n' + '\n'.join(flist) + "\n")
|
|
|
|
def cmd_dir(self, args):
|
|
return self.cmd_ls(args)
|
|
def cmd_list(self, args):
|
|
return self.cmd_ls(args)
|
|
|
|
def cmd_cd(self, args):
|
|
""" Change current directory """
|
|
d = args[0]
|
|
full = os.path.join(self.currentDir, d)
|
|
full = os.path.abspath(full)
|
|
if (len(full) < len(self.baseDir)):
|
|
self.sock.send("Already in root directory\n");
|
|
return
|
|
if (os.path.exists(full)):
|
|
if (os.path.isdir(full)):
|
|
self.currentDir = full
|
|
self.sock.send('Current directory: %s\n' %
|
|
self.currentDir[len(self.baseDir)+1:])
|
|
else:
|
|
self.sock.send('%s is not a directory\n' % d)
|
|
else:
|
|
self.sock.send('%s does not exist\n' % d)
|
|
|
|
def cmd_get(self, args):
|
|
fn = ' '.join(args)
|
|
full = os.path.join(self.currentDir, fn)
|
|
|
|
try:
|
|
size = os.path.getsize(full)
|
|
except OSError, e:
|
|
f = glob.glob(full)
|
|
if (len(f) == 1):
|
|
full = f[0]
|
|
size = os.path.getsize(full)
|
|
else:
|
|
self.sock.send('That matches more than one file\n')
|
|
return
|
|
min = self.caller.instantSendSize
|
|
if (size < min):
|
|
# Instant send
|
|
self.caller.spawnSend(self.irc, self.nick, self.hostmask, full)
|
|
else:
|
|
# Check queues
|
|
try:
|
|
posn = self.caller.addFile(self.irc, self.nick, self.hostmask, full)
|
|
self.sock.send('File queued at position %s\n' %
|
|
(posn))
|
|
self.caller.maybeSend()
|
|
except QueueException, e:
|
|
self.sock.send(e.msg + "\n")
|
|
|
|
def cmd_close(self, line):
|
|
self.sock.send('Bye!\n')
|
|
self.sock.close()
|
|
def cmd_quit(self, line):
|
|
self.cmd_close([])
|
|
|
|
def cmd_queues(self, args):
|
|
queues = self.caller.queuedFiles
|
|
lines = []
|
|
for q in range(len(queues)):
|
|
if (not args or queues[q][0] == args[0]):
|
|
fn = os.path.split(queues[q][1])[1]
|
|
when = time.ctime(queues[q][3])
|
|
lines.append('%s: %s (by %s@%s)' % (q, fn, queues[q][0], when[4:-5]))
|
|
if (lines):
|
|
self.sock.send('\n'.join(lines) + '\n')
|
|
else:
|
|
self.sock.send('There is currently nothing queued.\n')
|
|
|
|
def cmd_myqueues(self, args):
|
|
self.cmd_queues([self.msg.nick])
|
|
|
|
def cmd_sends(self, line):
|
|
sends = self.caller.sends
|
|
lines = []
|
|
for i in (range(len(sends))):
|
|
s = sends[i]
|
|
sender = s[4]
|
|
fn = os.path.split(s[1])[1]
|
|
secs = (sender.filesize - sender.currentPosition) / sender.currentSpeed
|
|
if (secs > 3600):
|
|
t = '%sh' % int(secs / 3600)
|
|
elif (secs > 60):
|
|
t = '%sm' % int(secs / 60)
|
|
else:
|
|
t = '%ss' % int(secs)
|
|
lines.append('%s: %s\n (to %s @ %sCPS, %s remaining)' % (i, fn, s[0], int(sender.currentSpeed), t))
|
|
|
|
if (lines):
|
|
self.sock.send('\n'.join(lines) + '\n')
|
|
else:
|
|
self.sock.send('There is currently nothing sending.\n')
|
|
|
|
def cmd_remove(self, args):
|
|
# remove a queue by number
|
|
idx = args[0]
|
|
if (not idx.isdigit()):
|
|
self.sock.send('Usage: remove <queue position>\n')
|
|
else:
|
|
posn = int(idx)
|
|
queues = self.caller.queuedFiles
|
|
if (len(queues) < posn or posn < 0):
|
|
self.sock.send('There is no such queue position\n')
|
|
else:
|
|
# XXX infintesimal race condition
|
|
del self.caller.queuedFiles[posn]
|
|
self.sock.send('Removed queue.\n')
|
|
|
|
def cmd_help(self, line):
|
|
pass
|
|
|
|
def chatConnected(self):
|
|
self.sock.send("Welcome.\n")
|
|
self.cmd_ls([])
|
|
|
|
def handleLine(self, line):
|
|
args = line.split()
|
|
if (not args):
|
|
return
|
|
if (not hasattr(self, 'cmd_%s' % args[0])):
|
|
self.sock.send('Unknown command: %s\n' % args[0])
|
|
else:
|
|
fn = getattr(self, 'cmd_%s' % args[0])
|
|
fn(args[1:])
|
|
|
|
|
|
class AdminHandler(ChatHandler):
|
|
|
|
def cmd_config(self, args):
|
|
pass
|
|
|
|
|
|
|
|
|
|
# --- Queue Object ---
|
|
|
|
class Queue:
|
|
name = ""
|
|
plugin = None
|
|
channel = ""
|
|
timeout = 0
|
|
maxSpeed = 0
|
|
minSpeed = 0
|
|
maxQueues = 0
|
|
instantSendSize = 0
|
|
maxSends = 0
|
|
receiveDir = ""
|
|
sendDir = ""
|
|
allowedModes = []
|
|
allowedMasks = []
|
|
allowedNicks = []
|
|
bannedNicks = []
|
|
bannedMasks = []
|
|
chatHandler = ""
|
|
sendHandler = ""
|
|
|
|
sends = []
|
|
queuedFiles = []
|
|
filesSent = 0
|
|
bytesSent = 0
|
|
leeches = {}
|
|
|
|
def __init__(self, plug, channel, name):
|
|
self.name = name
|
|
self.plugin = plug
|
|
self.channel = channel
|
|
|
|
try:
|
|
self.timeout = plug.registryValue("queues.%s.timeout" % name, channel)
|
|
except:
|
|
conf.registerGroup(conf.supybot.plugins.FServe.queues, name)
|
|
group = conf.supybot.plugins.FServe.queues.get(name)
|
|
conf.registerChannelValue(group, 'timeout', registry.Integer(90, ''))
|
|
conf.registerChannelValue(group, 'maxSpeed', registry.Integer(0, ''))
|
|
conf.registerChannelValue(group, 'minSpeed', registry.Integer(0, ''))
|
|
conf.registerChannelValue(group, 'maxQueues', registry.Integer(5, ''))
|
|
conf.registerChannelValue(group, 'instantSendSize',
|
|
registry.Integer(50000, ''))
|
|
conf.registerChannelValue(group, 'maxSends', registry.Integer(2, ''))
|
|
conf.registerChannelValue(group, 'receiveDir',
|
|
registry.String('incoming', ''))
|
|
conf.registerChannelValue(group, 'sendDir',
|
|
registry.String('files', ''))
|
|
conf.registerChannelValue(group, 'allowedModes',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'allowedMasks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'allowedNicks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'bannedMasks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'bannedNicks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'chatHandler',
|
|
registry.String('ChatHandler', ''))
|
|
conf.registerChannelValue(group, 'sendHandler',
|
|
registry.String('SendHandler', ''))
|
|
|
|
|
|
self.timeout = plug.registryValue("queues.%s.timeout" % name, channel)
|
|
self.maxSpeed = plug.registryValue("queues.%s.maxSpeed" % name,
|
|
channel)
|
|
self.minSpeed = plug.registryValue("queues.%s.minSpeed" % name,
|
|
channel)
|
|
self.maxQueues = plug.registryValue("queues.%s.maxQueues" % name,
|
|
channel)
|
|
self.instantSendSize = plug.registryValue("queues.%s.instantSendSize"
|
|
% name, channel)
|
|
self.maxSends = plug.registryValue("queues.%s.maxSends" % name,
|
|
channel)
|
|
self.receiveDir = plug.registryValue("queues.%s.receiveDir" % name,
|
|
channel)
|
|
self.sendDir = plug.registryValue("queues.%s.sendDir" % name, channel)
|
|
self.allowedModes = plug.registryValue("queues.%s.allowedModes"
|
|
% name, channel)
|
|
self.allowedMasks = plug.registryValue("queues.%s.allowedMasks"
|
|
% name, channel)
|
|
self.allowedNicks = plug.registryValue("queues.%s.allowedNicks"
|
|
% name, channel)
|
|
self.bannedNicks = plug.registryValue("queues.%s.bannedNicks" % name,
|
|
channel)
|
|
self.bannedMasks = plug.registryValue("queues.%s.bannedMasks" % name,
|
|
channel)
|
|
self.chatHandler = plug.registryValue("queues.%s.chatHandler" % name,
|
|
channel)
|
|
self.sendHandler = plug.registryValue("queues.%s.sendHandler" % name,
|
|
channel)
|
|
|
|
# End of Configurables
|
|
self.sends = []
|
|
self.queuedFiles = []
|
|
self.filesSent = 0
|
|
self.bytesSent = 0
|
|
self.leeches = {}
|
|
|
|
def spawnChat(self, irc, nick):
|
|
# first check permitted in queue
|
|
hostmask = irc.state.nickToHostmask(nick)
|
|
if (hostmask in self.bannedMasks or
|
|
(self.allowedMasks and not hostmask in self.allowedMasks)):
|
|
irc.reply("You are not allowed to use this queue.");
|
|
return
|
|
if (nick in self.bannedNicks or
|
|
(self.allowedNicks and not nick in self.allowedNicks)):
|
|
irc.reply("You are not allowed to use this queue.");
|
|
return
|
|
if (self.allowedModes):
|
|
ops = irc.state.channels[self.channel].ops
|
|
hops = irc.state.channels[self.channel].halfops
|
|
voices = irc.state.channels[self.channel].voices
|
|
users = irc.state.channels[self.channel].users
|
|
okay = 0
|
|
if (nick in ops and 'op' in self.allowedModes):
|
|
okay = 1
|
|
elif (nick in hops and 'halfop' in self.allowedModes):
|
|
okay = 1
|
|
elif (nick in voices and 'voice' in self.allowedModes):
|
|
okay = 1
|
|
elif (nick in users and 'user' in self.allowedModes):
|
|
okay = 1
|
|
if (not okay):
|
|
irc.reply("You are not allowed to use this queue.");
|
|
return
|
|
|
|
# We're okay to talk to. Spawn DCC Chat
|
|
parent = self.plugin._getHandlerClass(self.chatHandler)
|
|
new = parent(irc, nick, hostmask, queue=self)
|
|
new.start()
|
|
|
|
def spawnSend(self, irc, nick, hostmask, file):
|
|
parent = self.plugin._getHandlerClass(self.sendHandler)
|
|
cxn = parent(irc, nick, hostmask, file)
|
|
cxn.caller = self
|
|
self.sends.append([nick, hostmask, file, irc, cxn])
|
|
cxn.start()
|
|
|
|
def getConnectionByPort(self, port):
|
|
for s in self.sends:
|
|
if (s[-1].port == port):
|
|
return s[-1]
|
|
return None
|
|
|
|
def addFile(self, irc, nick, mask, file, posn=-1):
|
|
for q in self.queuedFiles:
|
|
if (q[0] == nick):
|
|
if (q[1] == file):
|
|
raise(QueueException(
|
|
'You have already queued that file'))
|
|
nqs += 1
|
|
if (nqs >= self.maxQueues):
|
|
raise(QueueException(
|
|
'You have already queued the maximum number of files'))
|
|
|
|
if (posn < 0 or posn > len(self.queuedFiles)):
|
|
# add to the end
|
|
self.queuedFiles.append([nick, mask, file, irc, time.time()])
|
|
return len(self.queuedFiles)
|
|
else:
|
|
self.queuedFiles = self.queuedFiles[:posn] + \
|
|
[[nick, mask, file, irc, time.time()]] + self.queuedFiles[posn:]
|
|
return posn
|
|
|
|
def sendFinished(self, handler):
|
|
# One send finished, so delete and check to see if we should start another
|
|
for s in range(len(self.sends)):
|
|
if (self.sends[s][-1] == handler):
|
|
del self.sends[s]
|
|
break
|
|
self.maybeSend()
|
|
|
|
def maybeSend(self):
|
|
# maybe send another queued file
|
|
if (self.queuedFiles and len(self.sends) < self.maxSends):
|
|
# find a send to someone we're not sending to already
|
|
sendingTo = []
|
|
for s in self.sends:
|
|
sendingTo.append(s[0])
|
|
for q in range(len(self.queuedFiles)):
|
|
if (not self.queuedFiles[q][0] in sendingTo):
|
|
#found a file to send
|
|
qo = self.queuedFiles[q]
|
|
del self.queuedFiles[q]
|
|
self.spawnSend(qo[3], qo[0], qo[1], qo[2])
|
|
break
|
|
|
|
def updateNick(self, old, new, mask):
|
|
for q in range(len(self.queues)):
|
|
if self.queues[q][0] == old and self.queues[q][1] == mask:
|
|
self.queues[q][0] = new
|
|
|
|
class ResumeReqHandler(dcc.ResumeReqHandler):
|
|
plugin = None
|
|
|
|
def _getSendHandler(self):
|
|
return self.plugin.getConnectionByPort(self.port)
|
|
|
|
|
|
class AcceptHandler(dcc.DCCReqHandler):
|
|
pass
|
|
|
|
|
|
|
|
# --- Plugin ---
|
|
|
|
class FServe(callbacks.Privmsg):
|
|
queues = {}
|
|
handlers = {'ChatHandler' : ChatHandler,
|
|
'SendHandler' : SendHandler}
|
|
requestHandlers = {'RESUME' : ResumeReqHandler,
|
|
'ACCEPT' : AcceptHandler,
|
|
'SEND' : dcc.SendReqHandler}
|
|
|
|
def _getHandlerClass(self, type):
|
|
return self.handlers.get(type, None)
|
|
|
|
def getConnectionByPort(self, port):
|
|
for chan in self.queues:
|
|
for q in self.queues[chan]:
|
|
qo = self.queues[chan][q]
|
|
s = qo.getConnectionByPort(port)
|
|
if s:
|
|
return s
|
|
|
|
|
|
def list(self, irc, msg, args):
|
|
""" Show list of queues for this channel """
|
|
channel = privmsgs.getChannel(msg, args)
|
|
qs = self.queues[channel].keys()
|
|
irc.reply('Known queues: %s' % ' '.join(qs))
|
|
|
|
|
|
def chat(self, irc, msg, args):
|
|
""" Start a DCC chat interface (FServe) """
|
|
channel = privmsgs.getChannel(msg, args)
|
|
if (len(args) != 1):
|
|
irc.reply("Unknown queue.")
|
|
return
|
|
name = args[0]
|
|
if (not self.queues.has_key(channel)):
|
|
# Silently ignore as we have no Queues here
|
|
return
|
|
queue = self.queues[channel].get(name, None)
|
|
if (queue):
|
|
queue.spawnChat(irc, msg.nick)
|
|
|
|
def add(self, irc, msg, args):
|
|
""" Add a queue """
|
|
channel = privmsgs.getChannel(msg, args)
|
|
name = args[0]
|
|
# First add to per channel config
|
|
current = self.registryValue('queueNames', channel)
|
|
if (name in current):
|
|
irc.reply("That queue already exists on this channel.")
|
|
else:
|
|
current.append(name)
|
|
self.setRegistryValue('queueNames', current, channel)
|
|
try:
|
|
self.registryValue('queues.%s' % name)
|
|
except registry.NonExistentRegistryEntry, e:
|
|
# Register channel name group
|
|
conf.registerGroup(conf.supybot.plugins.FServe.queues, name)
|
|
|
|
# And register configs
|
|
group = conf.supybot.plugins.FServe.queues.get(name)
|
|
conf.registerChannelValue(group, 'timeout', registry.Integer(90, ''))
|
|
conf.registerChannelValue(group, 'maxSpeed', registry.Integer(0, ''))
|
|
conf.registerChannelValue(group, 'minSpeed', registry.Integer(0, ''))
|
|
conf.registerChannelValue(group, 'maxQueues', registry.Integer(5, ''))
|
|
conf.registerChannelValue(group, 'instantSendSize',
|
|
registry.Integer(50000, ''))
|
|
conf.registerChannelValue(group, 'maxSends', registry.Integer(2, ''))
|
|
conf.registerChannelValue(group, 'receiveDir',
|
|
registry.String('incoming', ''))
|
|
conf.registerChannelValue(group, 'sendDir',
|
|
registry.String('files', ''))
|
|
conf.registerChannelValue(group, 'allowedModes',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'allowedMasks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'allowedNicks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'bannedMasks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'bannedNicks',
|
|
registry.SpaceSeparatedListOfStrings([], ''))
|
|
conf.registerChannelValue(group, 'chatHandler',
|
|
registry.String('ChatHandler', ''))
|
|
conf.registerChannelValue(group, 'sendHandler',
|
|
registry.String('SendHandler', ''))
|
|
|
|
self.queues[channel][name] = Queue(self, channel, name)
|
|
irc.reply('Created queue named %r' % name)
|
|
|
|
add = privmsgs.checkCapability(add, 'owner')
|
|
|
|
def remove(self, irc, msg, args):
|
|
""" Remove a queue """
|
|
channel = privmsgs.getChannel(msg, args)
|
|
name = args[0]
|
|
# Remove config only from list of Queues to build
|
|
current = self.registryValue('queueNames', channel)
|
|
if (name in current):
|
|
current.remove(name)
|
|
irc.reply('Removed queue named %r' % name)
|
|
else:
|
|
irc.reply('There is no such queue')
|
|
|
|
remove = privmsgs.checkCapability(remove, 'owner')
|
|
|
|
def doJoin(self, irc, msg):
|
|
""" Maybe build some internal representations of our config """
|
|
channel = msg.args[0]
|
|
if (ircutils.nickEqual(msg.nick, irc.nick)):
|
|
if (not self.queues.has_key(channel)):
|
|
queues = self.registryValue('queueNames', channel)
|
|
self.queues[channel] = {}
|
|
for q in queues:
|
|
self.queues[channel][q] = Queue(self, channel, q)
|
|
|
|
def doPrivmsg(self, irc, msg):
|
|
""" Maybe respond to DCC request """
|
|
if (ircutils.isCtcp(msg) and dcc.isDCC(msg)):
|
|
ctcpArgs = msg.args[1][1:-1].split()
|
|
dccType = ctcpArgs[1]
|
|
dccArgs = ctcpArgs[2:]
|
|
|
|
parent = self.requestHandlers[dccType]
|
|
handler = parent(irc, msg, dccArgs)
|
|
handler.plugin = self
|
|
handler.start()
|
|
|
|
|
|
def doNick(self, irc, msg):
|
|
""" Let queues know that nick has changed """
|
|
newNick = msg.args[0]
|
|
oldNick = msg.nick
|
|
hostmask = irc.state.nickToHostmask(msg.nick)
|
|
for chan in self.queues:
|
|
for queue in self.queues[chan]:
|
|
self.queues[chan][queue].updateNick(oldNick, newNick, hostmask)
|
|
|
|
Class = FServe
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|