| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
import xchat |
|---|
| 9 |
import sys |
|---|
| 10 |
import re |
|---|
| 11 |
from exceptions import ValueError |
|---|
| 12 |
|
|---|
| 13 |
__module_name__ = "xdccq" |
|---|
| 14 |
__module_version__ = "0.4" |
|---|
| 15 |
__module_description__ = "Xdcc Queue" |
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
__debugging__ = False |
|---|
| 19 |
|
|---|
| 20 |
if __debugging__: |
|---|
| 21 |
import traceback |
|---|
| 22 |
|
|---|
| 23 |
def print_debug(string): |
|---|
| 24 |
global __debugging__ |
|---|
| 25 |
if __debugging__: |
|---|
| 26 |
string = str(string) |
|---|
| 27 |
print "\00302" + string + "\003" |
|---|
| 28 |
|
|---|
| 29 |
def print_error(string): |
|---|
| 30 |
string = str(string) |
|---|
| 31 |
print "\00304" + string + "\003" |
|---|
| 32 |
|
|---|
| 33 |
def print_help(string): |
|---|
| 34 |
string = str(string) |
|---|
| 35 |
print "\00302" + string + "\003" |
|---|
| 36 |
|
|---|
| 37 |
def print_success(string): |
|---|
| 38 |
string = str(string) |
|---|
| 39 |
print "\00303" + string + "\003" |
|---|
| 40 |
|
|---|
| 41 |
def echo(string): |
|---|
| 42 |
string = str(string) |
|---|
| 43 |
print "\00300" + string + "\003" |
|---|
| 44 |
|
|---|
| 45 |
print_info = print_success |
|---|
| 46 |
|
|---|
| 47 |
cmd_help = {} |
|---|
| 48 |
gen_help = [ |
|---|
| 49 |
"\037[help, ?]\037 <cmd> - prints this help or detailed help for 'cmd'\n", |
|---|
| 50 |
"\037[ls, list]\037 - lists files in queue\n", |
|---|
| 51 |
"\037get\037 [bot] [#, #-#] - adds 'send' cmds to queue\n", |
|---|
| 52 |
"\037rm\037 [bot] <#, #-#> - removes 'send' cmds from queue\n", |
|---|
| 53 |
"\037[cancel,stop]\037 - cancles current transfer", |
|---|
| 54 |
] |
|---|
| 55 |
cmd_help['msg'] = " " + " ".join(gen_help) |
|---|
| 56 |
cmd_help['?'] = """\n\002/xdccq\002 \037[help, ?]\037 <cmd>\n \00303By itself, help/? prints out the available commands along withshort descriptions of each. If you supply a command \002cmd\002, a longer help description of that command and its usage is given.\003""" |
|---|
| 57 |
cmd_help['ls'] = """\n\002/xdccq\002 \037[ls, list]\037\n \00303Lists the file gets currently in the queue.\00303""" |
|---|
| 58 |
cmd_help['get'] = """\n\002/xdccq\002 \037get\037 [bot] [#, #-#]\n \00303Adds commands to the local queue "/ctcp \037bot\037 xdcc send \037#\037". A number ["7"], a number range ["1-5"], or a comma separated list ["7, 8-11, 15"] may be given.\003""" |
|---|
| 59 |
cmd_help['rm'] = """\n\003/xdccq\002 \037rm\037 [bot] <#, #-#>\n \00303Removes commands from local queue. If only \037bot\037 is supplied, it removes all commands dealing with \037bot\037. If it is supplied with a number or number range, any commands in that range are removed. If you had numbers "8, 9, 11, 12, 14" queued, and removed "8-14", it would remove all of those package numbers for that bot without resulting in an error for the missing "10" and "13".\003\n \00303New in 0.3: rm will now also call 'cancel' if the active transfer is from [bot] and the optional range argument was not given.\003""" |
|---|
| 60 |
cmd_help['cancel'] = """\n\003/xdccq\002 \037cancel\037\n \00303If a DCC handled by xdccq is currently active, it cancels that transfer and starts the next one on the queue. If the 'active' transfer is remotely queued, xdccq attempts to unqueue it by issuing a '/msg <bot> xdcc remove' command. If this command fails, this transfer will continue to remotely pend; take notice!\003""" |
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 |
def numToList(string): |
|---|
| 66 |
ret = [] |
|---|
| 67 |
numsplit = string.split(",") |
|---|
| 68 |
|
|---|
| 69 |
for n in numsplit: |
|---|
| 70 |
nr = n.split('-') |
|---|
| 71 |
|
|---|
| 72 |
if len(nr) == 1: |
|---|
| 73 |
try: ret.append(int(n)) |
|---|
| 74 |
except: raise ValueError("number") |
|---|
| 75 |
|
|---|
| 76 |
elif len(nr) == 2: |
|---|
| 77 |
try: |
|---|
| 78 |
low = int(nr[0]) |
|---|
| 79 |
high = int(nr[1]) + 1 |
|---|
| 80 |
if low > high: raise ValueError("number") |
|---|
| 81 |
ret += range(low, high) |
|---|
| 82 |
except ValueError: raise ValueError("number") |
|---|
| 83 |
else: raise ValueError("range") |
|---|
| 84 |
return ret |
|---|
| 85 |
|
|---|
| 86 |
class Command: |
|---|
| 87 |
def __init__(self, bot, num): |
|---|
| 88 |
self.bot = str(bot) |
|---|
| 89 |
self.num = int(num) |
|---|
| 90 |
self.channel = xchat.get_info("channel") |
|---|
| 91 |
self.network = xchat.get_info("network") |
|---|
| 92 |
self.context = xchat.get_context() |
|---|
| 93 |
self.file = "" |
|---|
| 94 |
self.retries = 0 |
|---|
| 95 |
self.queue_position = 0 |
|---|
| 96 |
self.queued = False |
|---|
| 97 |
self.transfering = False |
|---|
| 98 |
self.dead = False |
|---|
| 99 |
self.s = "ctcp %s xdcc send #%d" % (str(bot), int(num)) |
|---|
| 100 |
|
|---|
| 101 |
def __str__(self): |
|---|
| 102 |
return "#%d on %s (%s)" % (self.num, self.bot, self.channel) |
|---|
| 103 |
|
|---|
| 104 |
class Queue: |
|---|
| 105 |
def __init__(self): |
|---|
| 106 |
self.data = [] |
|---|
| 107 |
|
|---|
| 108 |
def put(self, item): |
|---|
| 109 |
self.data.append(item) |
|---|
| 110 |
|
|---|
| 111 |
def get(self): |
|---|
| 112 |
tmp = self.data[0] |
|---|
| 113 |
del self.data[0] |
|---|
| 114 |
return tmp |
|---|
| 115 |
|
|---|
| 116 |
def __getitem__(self, key): |
|---|
| 117 |
return self.data[key] |
|---|
| 118 |
|
|---|
| 119 |
def __delitem__(self, key): |
|---|
| 120 |
del self.data[key] |
|---|
| 121 |
|
|---|
| 122 |
def __len__(self): |
|---|
| 123 |
return len(self.data) |
|---|
| 124 |
|
|---|
| 125 |
def __iter__(self): |
|---|
| 126 |
return self.data.__iter__() |
|---|
| 127 |
|
|---|
| 128 |
class cmdQueue(Queue): |
|---|
| 129 |
"""Utility functions for dealing with a queue of Commands""" |
|---|
| 130 |
|
|---|
| 131 |
def getBotSet(self, botname, r=[]): |
|---|
| 132 |
packs = [cmd for cmd in self.data if cmd.bot == botname] |
|---|
| 133 |
|
|---|
| 134 |
if r: packs = [cmd for cmd in packs if cmd.num in r] |
|---|
| 135 |
return packs |
|---|
| 136 |
|
|---|
| 137 |
def removeBot(self, botname, r=[]): |
|---|
| 138 |
packs = self.getBotSet(botname, r) |
|---|
| 139 |
for cmd in packs: |
|---|
| 140 |
self.data.remove(cmd) |
|---|
| 141 |
|
|---|
| 142 |
return len(packs) |
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
CommandQueue = cmdQueue() |
|---|
| 146 |
Active = False |
|---|
| 147 |
Watchdog = False |
|---|
| 148 |
|
|---|
| 149 |
|
|---|
| 150 |
def help(a): |
|---|
| 151 |
global cmd_help |
|---|
| 152 |
if len(a) == 3: |
|---|
| 153 |
if cmd_help.has_key(a[2]): |
|---|
| 154 |
print_help(cmd_help[a[2]]) |
|---|
| 155 |
return |
|---|
| 156 |
usage() |
|---|
| 157 |
print cmd_help['msg'] |
|---|
| 158 |
|
|---|
| 159 |
|
|---|
| 160 |
def ls(a): |
|---|
| 161 |
global CommandQueue, Active |
|---|
| 162 |
s = "No files being transfered." |
|---|
| 163 |
if Active: |
|---|
| 164 |
if Active.transfering: |
|---|
| 165 |
dcc_list = xchat.get_list("dcc") |
|---|
| 166 |
cps = "Unknown CPS" |
|---|
| 167 |
for dcc_item in dcc_list: |
|---|
| 168 |
if str(dcc_item.nick).lower() == Active.bot.lower(): |
|---|
| 169 |
cps = "%s CPS" % (str(dcc_item.cps)) |
|---|
| 170 |
s = "Pack #%d from %s on %s@%s being transfered at %s." % (Active.num, Active.bot, Active.channel, Active.network, cps) |
|---|
| 171 |
elif Active.queued: s = "Pack #%d from %s on %s@%s queued remotely [position %d]." % (Active.num, Active.bot, Active.channel, Active.network, Active.queue_position) |
|---|
| 172 |
if len(CommandQueue) == 0: |
|---|
| 173 |
print_info("No files in the queue. " + s) |
|---|
| 174 |
else: |
|---|
| 175 |
print_info(s) |
|---|
| 176 |
for f in CommandQueue: |
|---|
| 177 |
print_info(f) |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
def get(a): |
|---|
| 181 |
if len(a) != 4: |
|---|
| 182 |
print_error("Error: invalid arguments for get. /xdccq get [bot] [#, #-#]") |
|---|
| 183 |
return |
|---|
| 184 |
bot = str(a[2]) |
|---|
| 185 |
try: nums = numToList(a[3]) |
|---|
| 186 |
except ValueError, exc: |
|---|
| 187 |
|
|---|
| 188 |
print_error("Error: %s contains invalid %s." % (a[3], exc.args[0])) |
|---|
| 189 |
return |
|---|
| 190 |
print_info("adding packs: %s" % (str(nums))) |
|---|
| 191 |
for num in nums: |
|---|
| 192 |
CommandQueue.put(Command(bot, num)) |
|---|
| 193 |
|
|---|
| 194 |
if len(nums): |
|---|
| 195 |
run() |
|---|
| 196 |
|
|---|
| 197 |
|
|---|
| 198 |
def rm(a): |
|---|
| 199 |
global CommandQueue, Active |
|---|
| 200 |
if len(a) not in (3, 4): |
|---|
| 201 |
print_error("Error: invalid arguments for 'rm'. /xdccq rm [bot] <#, #-#>") |
|---|
| 202 |
items_to_delete = [] |
|---|
| 203 |
if len(a) == 4: |
|---|
| 204 |
try: items_to_delete = numToList(a[3]) |
|---|
| 205 |
except ValueError, exc: |
|---|
| 206 |
|
|---|
| 207 |
print_error("Error: %s contains invalid %s." % (a[3], exc.args[0])) |
|---|
| 208 |
return |
|---|
| 209 |
|
|---|
| 210 |
removed = CommandQueue.removeBot(a[2], items_to_delete) |
|---|
| 211 |
if Active and Active.bot == a[2] and len(a) == 3: cancel() |
|---|
| 212 |
print_info("deleted %d commands from the queue." % removed) |
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
def cancel(a=[]): |
|---|
| 216 |
global Active |
|---|
| 217 |
if not Active: |
|---|
| 218 |
print_error("No currently transfering packages.") |
|---|
| 219 |
return |
|---|
| 220 |
if Active.transfering: |
|---|
| 221 |
s = "dcc close get %s %s" % (Active.bot, Active.file) |
|---|
| 222 |
print_debug("/%s" % (s)) |
|---|
| 223 |
Active.context.command(s) |
|---|
| 224 |
elif Active.queued: |
|---|
| 225 |
s = "msg %s xdcc remove" % (Active.bot) |
|---|
| 226 |
print_debug("/%s" % (s)) |
|---|
| 227 |
Active.context.command(s) |
|---|
| 228 |
Active = False |
|---|
| 229 |
run() |
|---|
| 230 |
|
|---|
| 231 |
USAGE_STR = "Usage: \002/xdccq\002 [cmd] [args], \002/xdccq\002 \037help\037 for commands." |
|---|
| 232 |
|
|---|
| 233 |
def usage(): |
|---|
| 234 |
print USAGE_STR |
|---|
| 235 |
|
|---|
| 236 |
def dispatch(argv, arg_to_eol, c): |
|---|
| 237 |
print_debug(argv) |
|---|
| 238 |
echo("/" + str(arg_to_eol[0])) |
|---|
| 239 |
try: |
|---|
| 240 |
{ |
|---|
| 241 |
"help" : help, |
|---|
| 242 |
"?" : help, |
|---|
| 243 |
"ls" : ls, |
|---|
| 244 |
"list" : ls, |
|---|
| 245 |
"get" : get, |
|---|
| 246 |
"rm" : rm, |
|---|
| 247 |
"cancel" : cancel, |
|---|
| 248 |
"stop" : cancel, |
|---|
| 249 |
}[argv[1]](argv) |
|---|
| 250 |
except: |
|---|
| 251 |
if __debugging__: traceback.print_exc(sys.stdout) |
|---|
| 252 |
usage() |
|---|
| 253 |
return xchat.EAT_XCHAT |
|---|
| 254 |
|
|---|
| 255 |
def retryTransfer(): |
|---|
| 256 |
if Active.retries < 3: |
|---|
| 257 |
Active.context.command(Active.s) |
|---|
| 258 |
Active.retries += 1 |
|---|
| 259 |
return True |
|---|
| 260 |
return False |
|---|
| 261 |
|
|---|
| 262 |
|
|---|
| 263 |
def transferCheck(data): |
|---|
| 264 |
global Active |
|---|
| 265 |
|
|---|
| 266 |
if not Active: |
|---|
| 267 |
return False |
|---|
| 268 |
elif Active.transfering or Active.queued: |
|---|
| 269 |
return False |
|---|
| 270 |
elif not Active.dead: |
|---|
| 271 |
Active.dead = True |
|---|
| 272 |
return True |
|---|
| 273 |
else: |
|---|
| 274 |
ret = retryTransfer() |
|---|
| 275 |
if ret: |
|---|
| 276 |
print_error("Previous attempt to get file (#%d from %s) seems to have failed. Repeating (%d of 3 retries)" % (Active.num, Active.bot, Active.retries)) |
|---|
| 277 |
return ret |
|---|
| 278 |
|
|---|
| 279 |
def run(): |
|---|
| 280 |
global CommandQueue, Active, Watchdog |
|---|
| 281 |
if Active: return |
|---|
| 282 |
if len(CommandQueue): |
|---|
| 283 |
cmd = CommandQueue.get() |
|---|
| 284 |
Active = cmd |
|---|
| 285 |
print_debug("/%s on %s@%s" % (cmd.s, cmd.channel, cmd.network)) |
|---|
| 286 |
Active.context.command(Active.s) |
|---|
| 287 |
|
|---|
| 288 |
Watchdog = xchat.hook_timer(45000, transferCheck) |
|---|
| 289 |
|
|---|
| 290 |
""" some other messages are possible: |
|---|
| 291 |
** Closing Connection: Unable to transfer data (Broken pipe) |
|---|
| 292 |
** Closing Connection: DCC Timeout (180 Sec Timeout) |
|---|
| 293 |
""" |
|---|
| 294 |
def notice(split, full, data): |
|---|
| 295 |
global CommandQueue, Active |
|---|
| 296 |
|
|---|
| 297 |
botname = split[0] |
|---|
| 298 |
message = split[1] |
|---|
| 299 |
if not Active: return |
|---|
| 300 |
if Active.queued: return |
|---|
| 301 |
if Active.bot == botname: |
|---|
| 302 |
re_queued = re.compile(r"queue") |
|---|
| 303 |
re_pos = re.compile(r"position [0-9]+") |
|---|
| 304 |
if re_queued.search(message.lower()): |
|---|
| 305 |
Active.queued = True |
|---|
| 306 |
print_info("xdccq detected the active file being placed on a remote queue") |
|---|
| 307 |
if re_pos.search(message.lower()): |
|---|
| 308 |
try: |
|---|
| 309 |
res = re_pos.search(message.lower()) |
|---|
| 310 |
postr = message[res.start() : res.end()] |
|---|
| 311 |
pos = int(postr.split()[1]) |
|---|
| 312 |
Active.queue_position = pos |
|---|
| 313 |
except: |
|---|
| 314 |
Active.queue_position = 0 |
|---|
| 315 |
print_error("an error occured parsing the remote queue position") |
|---|
| 316 |
|
|---|
| 317 |
def dccComplete(split, full, data): |
|---|
| 318 |
|
|---|
| 319 |
global CommandQueue, Active |
|---|
| 320 |
if not Active: return |
|---|
| 321 |
|
|---|
| 322 |
if Active.bot == str(split[2]): |
|---|
| 323 |
|
|---|
| 324 |
print_info("Received file \"%s\" from %s on %s@%s." % (Active.file, Active.bot, Active.channel, Active.network)) |
|---|
| 325 |
Active = False |
|---|
| 326 |
run() |
|---|
| 327 |
|
|---|
| 328 |
def dccConnect(split, full, data): |
|---|
| 329 |
global CommandQueue, Active |
|---|
| 330 |
if not Active: return |
|---|
| 331 |
|
|---|
| 332 |
botname = split[0] |
|---|
| 333 |
if Active.bot == str(split[0]): |
|---|
| 334 |
print_info("Requested file \"%s\" is being sent." % (split[2])) |
|---|
| 335 |
Active.file = split[2] |
|---|
| 336 |
Active.queued = False |
|---|
| 337 |
Active.transfering = True |
|---|
| 338 |
|
|---|
| 339 |
def dccStall(split, full, data): |
|---|
| 340 |
global CommandQueue, Active |
|---|
| 341 |
if not Active: return |
|---|
| 342 |
if Active.bot == str(split[2]): |
|---|
| 343 |
print_error("Requested file \"%s\" has stalled during transport." % (split[1])) |
|---|
| 344 |
ret = retryTransfer() |
|---|
| 345 |
if ret: |
|---|
| 346 |
print_info("Re-requesting file \"%s\" (%d of 3 retries)" % (split[1], Active.retries)) |
|---|
| 347 |
else: |
|---|
| 348 |
print_error("Retry limit reached for file \"%s\". Stopping the queue." % (split[1])) |
|---|
| 349 |
|
|---|
| 350 |
__unhook__ = xchat.hook_command("xdccq", dispatch, help=USAGE_STR) |
|---|
| 351 |
|
|---|
| 352 |
print_info("XdccQ-TNG loaded successfully") |
|---|
| 353 |
usage() |
|---|
| 354 |
|
|---|
| 355 |
noticeHook = xchat.hook_print("Notice", notice, "data") |
|---|
| 356 |
dccRecvCompleteHook = xchat.hook_print("DCC RECV Complete", dccComplete, "data") |
|---|
| 357 |
dccRecvConnectHook = xchat.hook_print("DCC RECV Connect", dccConnect, "data") |
|---|
| 358 |
dccRecvStallHook = xchat.hook_print("DCC Stall", dccStall, "data") |
|---|