mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 05:09:23 +01:00
We're probably going to use Joel's irclib.py, stripped for its DCC stuff.
This commit is contained in:
parent
35bd48b9e8
commit
65c8b48d40
@ -1,596 +0,0 @@
|
||||
#!/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:
|
377
src/dcc.py
377
src/dcc.py
@ -1,377 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) Robert Sanderson All rights reserved.
|
||||
# See LICENCE file in this distribution for details.
|
||||
|
||||
# Documentation on DCC specification
|
||||
# http://www.mirc.co.uk/help/dccresum.txt
|
||||
# www.irchelp.org/irchelp/rfc/dccspec.html
|
||||
# Also several other lesser known/implemented/used commands are floating around
|
||||
# but not implemented in major clients.
|
||||
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
|
||||
import log
|
||||
import conf
|
||||
import utils
|
||||
import world
|
||||
import socket
|
||||
import ircmsgs
|
||||
import ircutils
|
||||
import threading
|
||||
|
||||
def isDCC(msg):
|
||||
return msg.command == 'PRIVMSG' and msg.args[1][:5] == '\x01DCC '
|
||||
|
||||
|
||||
conf.registerGroup(conf.supybot.protocols, 'dcc')
|
||||
conf.registerGlobalValue(conf.supybot.protocols.dcc, 'timeout',
|
||||
registry.Integer(120, "Timeout on DCC sockets"))
|
||||
conf.registerGlobalValue(conf.supybot.protocols.dcc, 'packetSize',
|
||||
registry.Integer(1024, "Size of packet to send"))
|
||||
conf.registerGlobalValue(conf.supybot.protocols.dcc, 'chatLineLength',
|
||||
registry.Integer(1024, "Max size of line to read"))
|
||||
|
||||
# ---- Out Handlers ----
|
||||
|
||||
class DCCHandler:
|
||||
def __init__(self, irc, nick, hostmask, logger=None):
|
||||
self.irc = irc
|
||||
self.nick = nick
|
||||
self.hostmask = hostmask
|
||||
self.sock = None
|
||||
if logger:
|
||||
self.log = logger
|
||||
else:
|
||||
self.log = log
|
||||
|
||||
self.timeout = conf.supybot.protocols.dcc.timeout()
|
||||
|
||||
def start(self):
|
||||
t = threading.Thread(target=self.open)
|
||||
world.threadsSpawned += 1
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def open(self):
|
||||
# Override in subclasses to do something
|
||||
pass
|
||||
|
||||
def clientConnected(self):
|
||||
# Override in subclasses to do something when a client connects
|
||||
pass
|
||||
|
||||
def clientClosed(self):
|
||||
# Override to do something when a client closes connection
|
||||
pass
|
||||
|
||||
|
||||
class SendHandler(DCCHandler):
|
||||
""" Handle sending a file """
|
||||
def __init__(self, irc, nick, hostmask, filename, logger=None, start=0):
|
||||
DCCHandler.__init__(self, irc, nick, hostmask, logger)
|
||||
self.filename = filename
|
||||
self.startPosition = start
|
||||
self.currentPosition = 0
|
||||
self.filesize = 0
|
||||
self.currentSpeed = 0
|
||||
self.bytesSent = 0
|
||||
|
||||
def packetSent(self):
|
||||
pass
|
||||
|
||||
def registerPort(self):
|
||||
pass
|
||||
|
||||
def open(self):
|
||||
ip = conf.supybot.externalIP()
|
||||
try:
|
||||
self.filesize = os.path.getsize(self.filename)
|
||||
except OSError, e:
|
||||
self.log.warning('Requested file does not exist: %r',
|
||||
self.filename)
|
||||
return
|
||||
sock = utils.getSocket(ip)
|
||||
try:
|
||||
sock.bind((ip, 0))
|
||||
except socket.error, e:
|
||||
self.log.warning('Could not bind a socket to send.')
|
||||
return
|
||||
port = sock.getsockname()[1]
|
||||
self.port = port
|
||||
self.registerPort()
|
||||
i = ircutils.dccIP(ip)
|
||||
sock.listen(1)
|
||||
msg = ircmsgs.dcc(self.nick, 'SEND', self.filename, i, port, self.filesize)
|
||||
self.irc.queueMsg(msg)
|
||||
|
||||
# Wait for possible RESUME request to be handled which may change
|
||||
# self.startPosition on self
|
||||
# See Resume doc (URL in header)
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
(realSock, addr) = sock.accept()
|
||||
except socket.error, e:
|
||||
sock.close()
|
||||
self.log.info('%r: Send init errored with %s',
|
||||
self.filename, utils.exnToString(e))
|
||||
return
|
||||
self.log.info('%r: Sending to %s', self.filename, self.nick)
|
||||
|
||||
self.sock = realSock
|
||||
fh = file(self.filename)
|
||||
try:
|
||||
self.clientConnected()
|
||||
self.startTime = time.time()
|
||||
packetSize = conf.supybot.protocols.dcc.packetSize()
|
||||
try:
|
||||
slow = 0
|
||||
fh.seek(self.startPosition)
|
||||
self.currentPosition = fh.tell()
|
||||
while self.currentPosition < self.filesize:
|
||||
data = fh.read(min(packetSize, self.filesize - \
|
||||
self.currentPosition))
|
||||
self.sock.send(data)
|
||||
self.currentPosition = fh.tell()
|
||||
self.bytesSent += 1024
|
||||
self.packetSent()
|
||||
except socket.error, e:
|
||||
exn = utils.exnToString(e)
|
||||
self.log.info('%r: Send errored with %s', self.filename, exn)
|
||||
except SendException, e:
|
||||
exn = utils.exnToString(e)
|
||||
self.log.info('%r: Send aborted with %s', self.filename, exn)
|
||||
|
||||
self.endTime = time.time()
|
||||
duration = self.endTime - self.startTime
|
||||
self.log.info('%r: Sent %s/%s', self.filename, fh.tell(),
|
||||
self.filesize)
|
||||
# Sleep to allow client to finish reading data.
|
||||
# This is needed as we'll be here immediately after the final
|
||||
# packet
|
||||
time.sleep(1.0)
|
||||
self.clientClosed()
|
||||
finally:
|
||||
# Ensure that we're not leaking handles
|
||||
fh.close()
|
||||
self.sock.close()
|
||||
|
||||
|
||||
class ChatHandler(DCCHandler):
|
||||
""" Handle a DCC chat (initiate) """
|
||||
def lineReceived(self, line):
|
||||
# Override in subclasses to process a line of text
|
||||
pass
|
||||
|
||||
def open(self):
|
||||
ip = conf.supybot.externalIP()
|
||||
sock = utils.getSocket(ip)
|
||||
lineLength = conf.supybot.protocols.dcc.chatLineLength()
|
||||
try:
|
||||
sock.bind((ip, 0))
|
||||
except socket.error, e:
|
||||
self.log.error('Could not bind chat socket.')
|
||||
return
|
||||
port = sock.getsockname()[1]
|
||||
i = ircutils.dccIP(ip)
|
||||
sock.listen(1)
|
||||
msg = ircmsgs.dcc(self.nick, 'CHAT', 'chat', i, port)
|
||||
self.irc.queueMsg(msg)
|
||||
try:
|
||||
(realSock, addr) = sock.accept()
|
||||
except socket.timeout:
|
||||
sock.close()
|
||||
self.log.info('CHAT to %s timed out', self.nick)
|
||||
return
|
||||
self.log.info('CHAT accepted from %s', self.nick)
|
||||
realSock.settimeout(self.timeout)
|
||||
self.startTime = time.time()
|
||||
self.sock = realSock
|
||||
try:
|
||||
self.clientConnected()
|
||||
while 1:
|
||||
line = realSock.recv(lineLength)
|
||||
if line != "\n":
|
||||
self.lineReceived(line)
|
||||
except socket.error, e:
|
||||
self.log.info('CHAT ended with %s', self.nick)
|
||||
finally:
|
||||
self.sock.close()
|
||||
self.endTime = time.time()
|
||||
self.clientClosed()
|
||||
|
||||
|
||||
# ---- In Handlers ----
|
||||
|
||||
|
||||
class DCCReqHandler:
|
||||
def __init__(self, irc, msg, args, logger=None):
|
||||
self.irc = irc
|
||||
self.msg = msg
|
||||
self.args = args
|
||||
if logger:
|
||||
self.log = logger
|
||||
else:
|
||||
self.log = log
|
||||
|
||||
def start(self):
|
||||
t = threading.Thread(target=self.open)
|
||||
world.threadsSpawned += 1
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def clientConnected(self):
|
||||
pass
|
||||
|
||||
def clientClosed(self):
|
||||
pass
|
||||
|
||||
|
||||
class SendReqHandler(DCCReqHandler):
|
||||
""" We're being sent a file """
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
DCCReqHandler.__init__(self, *args, **kw)
|
||||
# This should be added to by subclasses
|
||||
self.incomingDir = conf.supybot.directories.data()
|
||||
self.filename = self.args[0]
|
||||
self.ip = ircutils.unDccIP(int(self.args[1]))
|
||||
self.port = int(self.args[2])
|
||||
self.filesize = int(self.args[3])
|
||||
self.filemode = 'w'
|
||||
|
||||
|
||||
def receivedPacket(self):
|
||||
# Override in subclass to do something with each packet received
|
||||
pass
|
||||
|
||||
def open(self):
|
||||
if (os.path.exists(self.filename)):
|
||||
currsize = os.path.getsize(self.filename)
|
||||
if (self.filesize > currsize):
|
||||
# Send RESUME DCC message and wait for ACCEPT
|
||||
# See AcceptReqHandler below
|
||||
msg = ircutils.dcc(self.nick, 'RESUME', self.filename,
|
||||
self.port, currsize)
|
||||
self.irc.queueMsg(msg)
|
||||
time.sleep(1)
|
||||
if self.filemode != 'a':
|
||||
# Didn't get an acknowledge for the RESUME
|
||||
# Zero file and read from scratch
|
||||
os.remove(self.filename)
|
||||
|
||||
sock = utils.getSocket(self.ip)
|
||||
try:
|
||||
sock.connect((self.ip, self.port))
|
||||
except socket.error, e:
|
||||
self.log.info('File receive could not connect')
|
||||
return
|
||||
|
||||
self.clientConnected()
|
||||
rootedName = os.path.abspath(os.path.join(self.incomingDir,
|
||||
self.filename))
|
||||
if not rootedName.startswith(self.incomingDir):
|
||||
self.log.warning('%s tried to send relative file', self.msg.nick)
|
||||
return
|
||||
|
||||
fh = file(rootedName, self.filemode)
|
||||
self.bytesReceived = 0
|
||||
self.startTime = time.time()
|
||||
pktSize = conf.supybot.protocols.dcc.packetSize()
|
||||
self.log.info('%r: Send starting from %s', self.filename,
|
||||
self.msg.nick))
|
||||
try:
|
||||
while self.bytesReceived < self.filesize:
|
||||
amnt = min(self.filesize - self.bytesReceived, pktSize)
|
||||
d = sock.recv(amnt)
|
||||
self.bytesReceived += len(d)
|
||||
# Required to send back packed integer to acknowledge receive
|
||||
sock.send(struct.pack("!I", self.bytesReceived))
|
||||
f.write(d)
|
||||
self.receivedPacket()
|
||||
except socket.error, e:
|
||||
exn = utils.exnToString(e)
|
||||
self.log.info('%r: Send died with %s', filename, exn)
|
||||
finally:
|
||||
self.endTime = time.time()
|
||||
sock.close()
|
||||
f.close()
|
||||
self.log.info('%r: Received %s/%s in %d seconds',
|
||||
self.filename, self.bytesReceived, self.filesize,
|
||||
self.endTime - self.startTime)
|
||||
self.clientClosed()
|
||||
|
||||
|
||||
class ResumeReqHandler(DCCReqHandler):
|
||||
|
||||
def _getSendHandler(self):
|
||||
# This will work in theory, BUT note well, if you instantiate
|
||||
# and do not override this to return the REAL SendHandler
|
||||
# the client may still get the original startPosition of 0
|
||||
# See RESUME documentation URL in header
|
||||
hostmask = self.irc.state.nickToHostmask(self.msg.nick)
|
||||
h = SendHandler(self.irc, self.msg.nick, hostmask, self.filename,
|
||||
start=self.startPosition)
|
||||
return h
|
||||
|
||||
def open(self):
|
||||
# filename is (apparently) ignored by mIRC
|
||||
# so don't depend on it.
|
||||
self.filename = self.args[0]
|
||||
self.port = int(self.args[1])
|
||||
self.startPosition = int(self.args[2])
|
||||
|
||||
msg = ircutils.dcc(self.msg.nick, "ACCEPT", self.filename, self.port,
|
||||
self.startPosition)
|
||||
self.irc.queueMsg(msg)
|
||||
cxn = self._getSendHandler()
|
||||
cxn.startPosition = self.startPosition
|
||||
self.log.info('%r: RESUME received for %s', self.filename,
|
||||
self.startPosition)
|
||||
|
||||
class AcceptReqHandler(DCCReqHandler):
|
||||
|
||||
def _getReceiveHandler(self):
|
||||
# We need the original SendReqHandler, which needs some cross request
|
||||
# logic that we don't provide.
|
||||
# The following may work, but this should be overridden
|
||||
h = SendReqHandler(self.irc, self.msg, self.args)
|
||||
return h
|
||||
|
||||
def open(self):
|
||||
self.filename = self.args[0]
|
||||
self.port = int(self.args[1])
|
||||
cxn = self._getReceiveHandler()
|
||||
cxn.filemode = 'a'
|
||||
self.log.info('%r: Got ACCEPT to resume file', self.filename)
|
||||
|
||||
|
||||
class ChatReqHandler(DCCReqHandler):
|
||||
|
||||
def open(self):
|
||||
ip = ircutils.unDccIP(int(self.args[1]))
|
||||
port = int(self.args[2])
|
||||
lineLength = conf.supybot.protocols.dcc.chatLineLength()
|
||||
|
||||
sock = utils.getSocket(ip)
|
||||
try:
|
||||
sock.connect((ip, port))
|
||||
except:
|
||||
self.log.error('Could not connect to chat socket.')
|
||||
return
|
||||
self.sock = sock
|
||||
sock.send('\n')
|
||||
try:
|
||||
while 1:
|
||||
line = sock.recv(lineLength)
|
||||
self.lineReceived(line)
|
||||
except socket.error, e:
|
||||
self.log.info('Chat finished')
|
||||
finally:
|
||||
sock.close()
|
||||
|
||||
|
||||
|
@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
###
|
||||
# Copyright (c) 2002, Jeremiah Fincher
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions, and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author of this software nor the name of
|
||||
# contributors to this software may be used to endorse or promote products
|
||||
# derived from this software without specific prior written consent.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
###
|
||||
|
||||
|
||||
from testsupport import *
|
||||
|
||||
import supybot.dcc as dcc
|
||||
import supybot.ircmsgs as ircmsgs
|
||||
|
||||
class FunctionsTestCase(SupyTestCase):
|
||||
def testIsDCC(self):
|
||||
self.failIf(dcc.isDCC(ircmsgs.privmsg('#supybot', '?DCC')))
|
||||
self.failIf(dcc.isDCC(ircmsgs.topic('#supybot', '?DCC')))
|
||||
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||
|
Loading…
Reference in New Issue
Block a user