Limnoria/plugins/FServe.py
Rob Sanderson 50c650ace3 Sample File server for DCC. Uses DCC Chat to navigate directory structure.
Allows multiple configurable queues on multiple channels.
-- Azaroth
2004-07-27 02:05:18 +00:00

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: