From b548c7ed40886c62d6af5a2a1ec78c02d1983a8d Mon Sep 17 00:00:00 2001 From: Stephen Dranger <dranger@gmail.com> Date: Tue, 15 Feb 2011 11:10:57 -0600 Subject: [PATCH] oyoyo --- TODO | 1 - irc.py | 299 ++++++++--------- irc.pyc | Bin 14194 -> 14119 bytes menus.py | 2 +- menus.pyc | Bin 25570 -> 25571 bytes oyoyo/client.py | 19 +- oyoyo/client.pyc | Bin 9938 -> 10126 bytes pesterchum.js | 2 +- pesterchum.py | 29 +- qt4reactor/.gitignore | 9 - qt4reactor/LICENSE | 57 ---- qt4reactor/README | 55 ---- qt4reactor/__init__.py | 0 qt4reactor/bin/gtrial | 24 -- qt4reactor/ghtTests/buttonStress.py | 57 ---- qt4reactor/ghtTests/fakeAppButtonStress.py | 51 --- qt4reactor/ghtTests/ircClient.py | 106 ------ qt4reactor/ghtTests/testIterate.py | 37 --- qt4reactor/ghtTests/trivialscript.py | 25 -- qt4reactor/gtrial.py | 41 --- qt4reactor/qt4reactor.py | 359 --------------------- qt4reactor/twisted/plugins/qt4.py | 9 - 22 files changed, 194 insertions(+), 988 deletions(-) delete mode 100644 qt4reactor/.gitignore delete mode 100644 qt4reactor/LICENSE delete mode 100644 qt4reactor/README delete mode 100644 qt4reactor/__init__.py delete mode 100755 qt4reactor/bin/gtrial delete mode 100755 qt4reactor/ghtTests/buttonStress.py delete mode 100755 qt4reactor/ghtTests/fakeAppButtonStress.py delete mode 100644 qt4reactor/ghtTests/ircClient.py delete mode 100755 qt4reactor/ghtTests/testIterate.py delete mode 100755 qt4reactor/ghtTests/trivialscript.py delete mode 100755 qt4reactor/gtrial.py delete mode 100644 qt4reactor/qt4reactor.py delete mode 100644 qt4reactor/twisted/plugins/qt4.py diff --git a/TODO b/TODO index 38c46ad..be2733d 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ Bugs: -* memos need to test for max time * color swatch text doesnt disappear * X and _ buttons move around all crazy like diff --git a/irc.py b/irc.py index 1a87048..dc6ae08 100644 --- a/irc.py +++ b/irc.py @@ -1,9 +1,10 @@ from PyQt4 import QtGui, QtCore -from twisted.internet.protocol import ClientFactory -from twisted.words.protocols.irc import IRCClient -from twisted.internet import reactor +from oyoyo.client import IRCClient +from oyoyo.cmdhandler import DefaultCommandHandler +from oyoyo import helpers import logging import random +import socket from dataobjs import Mood, PesterProfile from generic import PesterList @@ -17,32 +18,31 @@ class PesterIRC(QtCore.QObject): self.config = config def IRCConnect(self): server = self.config.server() - port = int(self.config.port()) - nick = self.mainwindow.profile() - self.cli = PesterIRCFactory(nick, self) - logging.info("---> Logging on...") - reactor.connectTCP(server, port, self.cli) - reactor.run() + port = self.config.port() + self.cli = IRCClient(PesterHandler, host=server, port=int(port), nick=self.mainwindow.profile().handle, real_name='pcc30', blocking=True) + self.cli.command_handler.parent = self + self.cli.command_handler.mainwindow = self.mainwindow + self.conn = self.cli.connect() + self.brokenConnection = False def closeConnection(self): - #logging.info("---> Logging on...") - # self.cli.close() - pass + self.cli.close() + def setConnectionBroken(self, broken=True): + self.brokenConnection = True @QtCore.pyqtSlot(PesterProfile) def getMood(self, *chums): - self.cli.getMood(*chums) + self.cli.command_handler.getMood(*chums) @QtCore.pyqtSlot(PesterList) def getMoods(self, chums): - self.cli.getMood(*chums) + self.cli.command_handler.getMood(*chums) @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def sendMessage(self, text, handle): h = unicode(handle) textl = [unicode(text)] - CMD_LENGTH = 450 def splittext(l): - if len(l[0]) > CMD_LENGTH: - space = l[0].rfind(" ", 0,CMD_LENGTH) + if len(l[0]) > 400: + space = l[0].rfind(" ", 0,400) if space == -1: - space = CMD_LENGTH + space = 400 a = l[0][0:space] b = l[0][space:] if len(b) > 0: @@ -52,72 +52,117 @@ class PesterIRC(QtCore.QObject): else: return l textl = splittext(textl) - for t in textl: - self.cli.msg(h, t) + try: + for t in textl: + helpers.msg(self.cli, h, t) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString, bool) def startConvo(self, handle, initiated): h = unicode(handle) - if initiated: - self.cli.msg(h, "PESTERCHUM:BEGIN") - self.cli.msg(h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())) + try: + if initiated: + helpers.msg(self.cli, h, "PESTERCHUM:BEGIN") + helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def endConvo(self, handle): h = unicode(handle) - self.cli.msg(h, "PESTERCHUM:CEASE") + try: + helpers.msg(self.cli, h, "PESTERCHUM:CEASE") + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot() def updateProfile(self): me = self.mainwindow.profile() handle = me.handle - self.cli.setNick(handle) + try: + helpers.nick(self.cli, handle) + except socket.error: + self.setConnectionBroken() self.updateMood() @QtCore.pyqtSlot() def updateMood(self): me = self.mainwindow.profile() - self.cli.msg("#pesterchum", "MOOD >%d" % (me.mood.value())) + try: + helpers.msg(self.cli, "#pesterchum", "MOOD >%d" % (me.mood.value())) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot() def updateColor(self): me = self.mainwindow.profile() for h in self.mainwindow.convos.keys(): - self.cli.msg(h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())) + try: + helpers.msg(self.cli, h, "COLOR >%s" % (self.mainwindow.profile().colorcmd())) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def blockedChum(self, handle): h = unicode(handle) - self.cli.msg(h, "PESTERCHUM:BLOCK") + try: + helpers.msg(self.cli, h, "PESTERCHUM:BLOCK") + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def unblockedChum(self, handle): h = unicode(handle) - self.cli.msg(h, "PESTERCHUM:UNBLOCK") + try: + helpers.msg(self.cli, h, "PESTERCHUM:UNBLOCK") + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def requestNames(self, channel): c = unicode(channel) - self.cli.sendMessage("NAMES", c) + try: + helpers.names(self.cli, c) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot() def requestChannelList(self): - self.cli.sendMessage("LIST") + try: + helpers.channel_list(self.cli) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def joinChannel(self, channel): c = unicode(channel) - self.cli.join(c) + try: + helpers.join(self.cli, c) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString) def leftChannel(self, channel): c = unicode(channel) - self.cli.leave(c) + try: + helpers.part(self.cli, c) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString, QtCore.QString) def kickUser(self, handle, channel): c = unicode(channel) h = unicode(handle) - self.cli.kick(h, c) + try: + helpers.kick(self.cli, h, c) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot(QtCore.QString, QtCore.QString, QtCore.QString) def setChannelMode(self, channel, mode, command): c = unicode(channel) - m = unicode(mode).replace("+", "") + m = unicode(mode) cmd = unicode(command) if cmd == "": cmd = None - self.cli.mode(c, True, m, cmd) + try: + helpers.mode(self.cli, c, m, cmd) + except socket.error: + self.setConnectionBroken() @QtCore.pyqtSlot() def reconnectIRC(self): - pass + self.setConnectionBroken() + + def updateIRC(self): + self.conn.next() moodUpdated = QtCore.pyqtSignal(QtCore.QString, Mood) colorUpdated = QtCore.pyqtSignal(QtCore.QString, QtGui.QColor) @@ -129,48 +174,10 @@ class PesterIRC(QtCore.QObject): nickCollision = QtCore.pyqtSignal(QtCore.QString, QtCore.QString) connected = QtCore.pyqtSignal() userPresentUpdate = QtCore.pyqtSignal(QtCore.QString, QtCore.QString, - QtCore.QString) - - -class PesterIRCClient(IRCClient): - realname = "pcc30" - username = "pcc30" - - def __init__(self, nick, qobj): - self.nickname = nick - self.parent = qobj - self.mainwindow = qobj.mainwindow - qobj.irc = self - - def msg(self, user, message): - logging.info("---> send PRIVMSG %s %s" % (user, message)) - IRCClient.msg(self, user, message) - - def signedOn(self): - logging.info("---> recv WELCOME") - self.parent.connected.emit() - self.join("#pesterchum") - mychumhandle = self.mainwindow.profile().handle - mymood = self.mainwindow.profile().mood.value() - self.msg("#pesterchum", "MOOD >%d" % (mymood)) - - chums = self.mainwindow.chumList.chums - self.getMood(*chums) - - def getMood(self, *chums): - chumglub = "GETMOOD " - for c in chums: - chandle = c.handle - if len(chumglub+chandle) >= 350: - self.msg("#pesterchum", chumglub) - chumglub = "GETMOOD " - chumglub += chandle - if chumglub != "GETMOOD ": - self.msg("#pesterchum", chumglub) + QtCore.QString) +class PesterHandler(DefaultCommandHandler): def privmsg(self, nick, chan, msg): - # do we still need this? - #msg = msg.decode("utf-8") # display msg, do other stuff if len(msg) == 0: return @@ -178,7 +185,7 @@ class PesterIRCClient(IRCClient): if msg[0] == '\x01': return handle = nick[0:nick.find("!")] - logging.info("---> recv PRIVMSG %s :%s" % (handle, msg)) + logging.info("---> recv \"PRIVMSG %s :%s\"" % (handle, msg)) if chan == "#pesterchum": # follow instructions if msg[0:6] == "MOOD >": @@ -191,7 +198,8 @@ class PesterIRCClient(IRCClient): mychumhandle = self.mainwindow.profile().handle mymood = self.mainwindow.profile().mood.value() if msg.find(mychumhandle, 8) != -1: - self.msg("#pesterchum", "MOOD >%d" % (mymood)) + helpers.msg(self.client, "#pesterchum", + "MOOD >%d" % (mymood)) elif chan[0] == '#': if msg[0:16] == "PESTERCHUM:TIME>": self.parent.timeCommand.emit(chan, handle, msg[16:]) @@ -212,88 +220,89 @@ class PesterIRCClient(IRCClient): self.parent.colorUpdated.emit(handle, color) else: self.parent.messageReceived.emit(handle, msg) - - def irc_ERR_NICKNAMEINUSE(self, prefix, params): - logging.info("---> recv NICKINUSE %s %s" % (prefix, params)) + + + def welcome(self, server, nick, msg): + self.parent.connected.emit() + helpers.join(self.client, "#pesterchum") + mychumhandle = self.mainwindow.profile().handle + mymood = self.mainwindow.profile().mood.value() + helpers.msg(self.client, "#pesterchum", "MOOD >%d" % (mymood)) + + chums = self.mainwindow.chumList.chums + self.getMood(*chums) + + def nicknameinuse(self, server, cmd, nick, msg): newnick = "pesterClient%d" % (random.randint(100,999)) - self.setNick(newnick) + helpers.nick(self.client, newnick) self.parent.nickCollision.emit(nick, newnick) - def userQuit(self, nick, reason): - logging.info("---> recv QUIT %s %s" % (nick, reason)) + def quit(self, nick, reason): handle = nick[0:nick.find("!")] self.parent.userPresentUpdate.emit(handle, "", "quit") self.parent.moodUpdated.emit(handle, Mood("offline")) - def userKicked(self, kickee, channel, kicker, msg): - logging.info("---> recv KICK %s %s %s %s" % (kickee, channel, kicker, msg)) - self.parent.userPresentUpdate.emit(kickee, channel, "kick:%s" % (op)) + def kick(self, opnick, channel, handle, op): + self.parent.userPresentUpdate.emit(handle, channel, "kick:%s" % (op)) # ok i shouldnt be overloading that but am lazy - def userLeft(self, nick, channel, reason="nanchos"): - logging.info("---> recv LEFT %s %s" % (nick, channel)) + def part(self, nick, channel, reason="nanchos"): handle = nick[0:nick.find("!")] self.parent.userPresentUpdate.emit(handle, channel, "left") if channel == "#pesterchum": self.parent.moodUpdated.emit(handle, Mood("offline")) - def userJoined(self, nick, channel): - logging.info("---> recv JOIN %s %s" % (nick, channel)) + def join(self, nick, channel): handle = nick[0:nick.find("!")] self.parent.userPresentUpdate.emit(handle, channel, "join") if channel == "#pesterchum": self.parent.moodUpdated.emit(handle, Mood("chummy")) - def modeChannel(self, op, channel, set, modes, args): - logging.info("---> recv MODE %s %s %s %s %s" % (op, channel, set, modes, args)) - if set: - modes += "+" - else: - modes += "-" - handle = "" - print args + def mode(self, op, channel, mode, handle=""): self.parent.userPresentUpdate.emit(handle, channel, mode) - def userRenamed(self, oldnick, newnick): - logging.info("---> recv RENAME %s %s" % (oldnick, newnick)) + def nick(self, oldnick, newnick): + oldhandle = oldnick[0:oldnick.find("!")] newchum = PesterProfile(newnick, chumdb=self.mainwindow.chumdb) - self.parent.moodUpdated.emit(oldnick, Mood("offline")) - self.parent.userPresentUpdate.emit("%s:%s" % (oldnick, newnick), "", "nick") + self.parent.moodUpdated.emit(oldhandle, Mood("offline")) + self.parent.userPresentUpdate.emit("%s:%s" % (oldhandle, newnick), "", "nick") if newnick in self.mainwindow.chumList.chums: self.getMood(newchum) - def irc_RPL_NAMREPLY(self, prefix, params): - logging.info("---> recv NAMREPLY %s %s" % (prefix, params)) - # namelist = names.split(" ") - # logging.info("---> recv \"NAMES %s: %d names\"" % (channel, len(namelist))) - # if not hasattr(self, 'channelnames'): - # self.channelnames = {} - # if not self.channelnames.has_key(channel): - # self.channelnames[channel] = [] - # self.channelnames[channel].extend(namelist) - def irc_RPL_ENDOFNAMES(self, prefix, params): - logging.info("---> recv ENDOFNAMES %s %s" % (prefix, params)) - # namelist = self.channelnames[channel] - # pl = PesterList(namelist) - # del self.channelnames[channel] - # self.parent.namesReceived.emit(channel, pl) - def irc_RPL_LISTSTART(self, prefix, params): - logging.info("---> recv LISTSTART %s %s" % (prefix, params)) - # self.channel_list = [] - # info = list(info) - # self.channel_field = info.index("Channel") # dunno if this is protocol - def irc_RPL_LIST(self, prefix, params): - logging.info("---> recv LIST %s %s" % (prefix, params)) - # channel = info[self.channel_field] - # if channel not in self.channel_list and channel != "#pesterchum": - # self.channel_list.append(channel) - def irc_RPL_LISTEND(self, prefix, params): - logging.info("---> recv LISTEND %s %s" % (prefix, params)) - # pl = PesterList(self.channel_list) - # self.parent.channelListReceived.emit(pl) - # self.channel_list = [] + def namreply(self, server, nick, op, channel, names): + namelist = names.split(" ") + logging.info("---> recv \"NAMES %s: %d names\"" % (channel, len(namelist))) + if not hasattr(self, 'channelnames'): + self.channelnames = {} + if not self.channelnames.has_key(channel): + self.channelnames[channel] = [] + self.channelnames[channel].extend(namelist) + def endofnames(self, server, nick, channel, msg): + namelist = self.channelnames[channel] + pl = PesterList(namelist) + del self.channelnames[channel] + self.parent.namesReceived.emit(channel, pl) + + def liststart(self, server, handle, *info): + self.channel_list = [] + info = list(info) + self.channel_field = info.index("Channel") # dunno if this is protocol + def list(self, server, handle, *info): + channel = info[self.channel_field] + if channel not in self.channel_list and channel != "#pesterchum": + self.channel_list.append(channel) + def listend(self, server, handle, msg): + pl = PesterList(self.channel_list) + self.parent.channelListReceived.emit(pl) + self.channel_list = [] -class PesterIRCFactory(ClientFactory): - protocol = PesterIRCClient - - def __init__(self, nick, qobj): - self.irc = self.protocol(nick, qobj) - def buildProtocol(self, addr=None): - return self.irc - def clientConnectionLost(self, conn, reason): - conn.connect() - def clientConnectionFailed(self, conn, reason): - conn.connect() + def getMood(self, *chums): + chumglub = "GETMOOD " + for c in chums: + chandle = c.handle + if len(chumglub+chandle) >= 350: + try: + helpers.msg(self.client, "#pesterchum", chumglub) + except socket.error: + self.parent.setConnectionBroken() + chumglub = "GETMOOD " + chumglub += chandle + if chumglub != "GETMOOD ": + try: + helpers.msg(self.client, "#pesterchum", chumglub) + except socket.error: + self.parent.setConnectionBroken() + diff --git a/irc.pyc b/irc.pyc index a7a711649de4e26496e6d430e1fa25e01f134bb4..301298b02dd19931612ca0af69486cfd39e4774f 100644 GIT binary patch literal 14119 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 ZXqFLu*F1bF|M0$YG=rziOlI+K{}-rugI@pu literal 14194 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 diff --git a/menus.py b/menus.py index c6be3a1..e68aea9 100644 --- a/menus.py +++ b/menus.py @@ -649,6 +649,6 @@ class LoadingScreen(QtGui.QDialog): class AboutPesterchum(QtGui.QMessageBox): def __init__(self, parent=None): QtGui.QMessageBox.__init__(self, parent) - self.setText("P3ST3RCHUM V. 3.14 alpha 4") + self.setText("P3ST3RCHUM V. 3.1.4 alpha 4") self.setInformativeText("Programming by illuminatedwax (ghostDunk), art by Grimlive (aquaMarinist)") self.mainwindow = parent diff --git a/menus.pyc b/menus.pyc index 8a86c4c53026c2a0a4186fabc5c49b127742422b..23b319bb18a484dbbd7b1f0568555f1ce386319d 100644 GIT binary patch delta 46 zcmaEKobmB-M)r%Jc)7}bqc*bVCyGfkFfard2ZtC3IeUcqDun4N80#78ZSGDCX956# C4i07j delta 45 zcmaESobl0dM)r%Jc)4DGjNHhcpC~HDz`zh-92{aC<m?gZs}QEAV611jxhFB42>_P( B4^998 diff --git a/oyoyo/client.py b/oyoyo/client.py index fa60fa1..da5a716 100644 --- a/oyoyo/client.py +++ b/oyoyo/client.py @@ -29,8 +29,6 @@ from oyoyo.parse import * from oyoyo import helpers from oyoyo.cmdhandler import CommandError -from datetime import * - # Python < 3 compatibility if sys.version_info < (3,): class bytes(object): @@ -127,7 +125,18 @@ class IRCClient: msg = bytes(" ", "ascii").join(bargs) logging.info('---> send "%s"' % msg) - self.socket.send(msg + bytes("\r\n", "ascii")) + try: + self.socket.send(msg + bytes("\r\n", "ascii")) + except socket.error, se: + try: # a little dance of compatibility to get the errno + errno = e.errno + except AttributeError: + errno = e[0] + if not self.blocking and errno == 11: + print "O WELLS" + pass + else: + raise e def connect(self): """ initiates the connection to the server set in self.host:self.port @@ -157,16 +166,14 @@ class IRCClient: buffer = bytes() while not self._end: try: - #logfile.write("recv at %s\n" % datetime.now().strftime("%Y-%m-%d.%H.%M %S")) buffer += self.socket.recv(1024) - #logfile.write("recvd %s at %s\n" % (buffer, datetime.now().strftime("%Y-%m-%d.%H.%M %S"))) - #logfile.flush() except socket.error, e: try: # a little dance of compatibility to get the errno errno = e.errno except AttributeError: errno = e[0] if not self.blocking and errno == 11: + print "O WELLS" pass else: raise e diff --git a/oyoyo/client.pyc b/oyoyo/client.pyc index f289bd05b4d34627cbf00bf16839959826db0dbe..9cc24225e5ffbe94dc4b2b808f89bbe09431f791 100644 GIT binary patch delta 1153 zcmY*YU1$_n6h3EmXE(F|KkjCC!^YigHd%?97(p7)HU?7@o9xyJaidj{W=5D0cTILs zQerK(FFq*hLC}Iqi%)_DnU_8ld?=)-1@)=4P#=8oML{1#eX-|EB-nxT&AHz>_s;#k zd*{*Q`C7-rSK+{=+JVVg`0zyeJ<QR9iyc=_n+jMFFd{Gq(98iEJ}{$j6v6Pr4*=f- z>^~5L26Ckh%(z?8)+z}w0$@bJ2(D2{FhXF&zzDBV>%c&ms>J!D)_Z=c739<waCTr4 zu|#{76WB#7%4O_z4trMGaDaM4rAUBVBx)S3pPiXd96d`#AS;|dU2n{T*A9afg}DGU z4*}w-Ho&DY*fFr;a7u%EBhM`fw;ty@B9{yevRrE@fHTXyqa=iyX~1Pow9F~c)L3f} zc8@Jwcl*!aYA?&0s)S`Dw-kVab2(h_WGx<p6%^LbP%G3&Cnlz<m38!M>`cs7nKKs` zn~SbklkTPRem%=%B|zKbpK3O*1T9nFLNBw!fz3SlT8Kfq8!vA3V3E!7U>GA9#SE(G zRc6IBjU&P#N7{PJ%r}3~yZG%uJBpqN91qc*L_Q++c)^OT+C%>&whxL<4;bp2PM_%f z4RKik(YY+1pm@T@@(nQBXf!!B)I_hHahLJCE-o|?XBg^s7WU!79l&~>ZI!Qkq1g#& zbH-OFKo64{Rb$_R&Wq&d82rd1+L}5a)%#h3zC~b=fu5y~`t<>qqkZW<-0J!=#Kbv{ z))?JScg8UHe_QBHdKyRQLiZkZgu8yHYwZI*nWK-;x5{X{Sk))f<tF(&K1CbU`m|4+ z{)|t-KWSUeQ1@`<0o}+w!N<;b9X_RFf{ALy%cyS@*d!ob`Xv37&*B7c>@#|okKkX9 z)_D`LO24c>iBFxL!jiISr!>l))idrC23rozvdu+~V=es);S&Nwbib>B{q(%6$g4(6 z8*<Y^R|R?*+&k2oH_hYvm!y}vQhS9IDYNsI*{}_>HLDNM*-|go#VvK1OV8<b=_=M- zPmb`oo&N1!-#y0Js)(Xw?%s6CW(0->j)-IGIHxN#U+%*cT`5;^n(mgfc#!@s@1Sx| jG4_?T%CDe{xB56w+C@M1B(OlYdvcnGk4Q~>lh^(O5H953 delta 1040 zcmX|AOKcNo5T4mxJNCx!7~8Q)aAM;)3Xm$IO<VDZ+LAy+99Y~q2|@_@-%vXtPU2Oo zRB4f#9%-d^S|RQg2c#URFXcjoKs*E&5GSNchyynyipqrx%xqA8X+DqV`OS>>m&r#H znTM}}CcK=PsKWaf;olLKrul2;(s>1-G?))T+18*I1{H!jKqC#PykJLQ4neiSPXOxx zGqX`xME3Z=j=3KHmZt-h35xUEw@h(R0Z<W8!7bA^PzWcAQEp#*%dgbx$1cR3G^Txn zdAhFsf&Hzre%FUX)D_5ud>rU!nH0W*tx_P0xi!uWLM;SqZQvNdWSHr?2FZ1y=I8Qe z>vgbS*Gt7P4M%^}iavS~JwX4Y_nUjT4o?itZu%kCld2-?Qa{8Z3u^w)mhW!S{n*Bb zF_carSZYIDO~V?3{5RYs8NkhkzelJno&|$h)FMzfp)pMx9s6QYz%?C|2kII$48Y}U zzr~Y=mlsKy_=%}H8cxa$Zv*7FTt}Q{s5qT4h09j~D;1t<vs<GZ(BZy(2ER0QNpZil z90cVW(;O=^--?eSB@#cIj)y0)S5_J;bT8UPe<kL8jtpvBpU{rv01i@EANgo6TllQd z9A$uj&UeV7iE+IHJ2exdnjdvAQP<LJ(J_c23}cFJCciR9IDy_~#yp~2r>|2Vc%_;( zLfOorr0jAxOVecV>R!!1<j?v)ZM{i(HM~QZQJy30xlQ%VQ+&`G-|p2i<7_Au4MuC1 zfaF^eWgVr3?oJ$M$_K-aoa&5HpD)zxMdwlLard8y6SSKB7XNMimtEC%AC#1mK*~*K zaFc3|y~1*?X?-d7n804Tn9JhF^mnd@>p#i$r^{lV5a?rY*VtUJ?ImlHy7Il-j)_r* zs=ED^-K<)lQ90j-Q?gOd0d_s78~ICyFmM-inw1D$*|{S>#@gmYJ)*hvj*C_ikW}lm ztj}|p)f+Ti7{CNo3q>r`g+eEuq|1f<0r?!%8OL^N=WKdb=#9)uUQj@`ZXM<dUr;#U Yp;LW9?4|j>bh~Ei5lnj$M#t~(0Dj8M1poj5 diff --git a/pesterchum.js b/pesterchum.js index 185587d..aaa058c 100644 --- a/pesterchum.js +++ b/pesterchum.js @@ -1 +1 @@ -{"tabs": true, "soundon": true, "server": "irc.tymoon.eu", "chums": ["unknownTraveler", "tentacleTherapist", "vaginalEngineer", "mechanicalSpectacle", "carcinoGeneticist", "schlagzeugGator", "gamblingGenocider", "gardenGnostic", "superGhost", "centaursTesticle", "arachnidsGrip", "grimAuxiliatrix", "remoteBloodbath", "nitroZealist", "greenZephyr", "arsenicCatnip", "adiosToreador", "cuttlefishCuller", "rageInducer", "gallowsCalibrator", "caligulasAquarium", "terminallyCapricious", "illuminatedWax", "aquaMarinist", "elegantDiversion", "moirailBunp", "uroborosUnbound", "androidTechnician", "midnightSparrow", "apocalypseArisen", "anguillaNuntia", "oilslickOrchid", "confusedTransient", "pretentiousFantasia", "aquaticMarinist", "lyricalKeraunoscopic", "counterRealist", "ectoBiologist", "percipientPedestrian", "asceticClinician", "doctectiveMiracles", "noSense", "obliviousCrafter", "ircMonster"], "defaultprofile": "testProfile", "block": []} \ No newline at end of file +{"tabs": true, "soundon": true, "server": "irc.tymoon.eu", "chums": ["unknownTraveler", "tentacleTherapist", "vaginalEngineer", "mechanicalSpectacle", "carcinoGeneticist", "schlagzeugGator", "gamblingGenocider", "gardenGnostic", "superGhost", "centaursTesticle", "arachnidsGrip", "grimAuxiliatrix", "remoteBloodbath", "nitroZealist", "greenZephyr", "arsenicCatnip", "adiosToreador", "cuttlefishCuller", "rageInducer", "gallowsCalibrator", "caligulasAquarium", "terminallyCapricious", "illuminatedWax", "aquaMarinist", "elegantDiversion", "moirailBunp", "uroborosUnbound", "androidTechnician", "midnightSparrow", "apocalypseArisen", "anguillaNuntia", "oilslickOrchid", "confusedTransient", "pretentiousFantasia", "aquaticMarinist", "lyricalKeraunoscopic", "counterRealist", "ectoBiologist", "percipientPedestrian", "asceticClinician", "doctectiveMiracles", "noSense", "obliviousCrafter", "ircMonster"], "defaultprofile": "ghostDunk", "block": []} \ No newline at end of file diff --git a/pesterchum.py b/pesterchum.py index c887918..f645fd5 100644 --- a/pesterchum.py +++ b/pesterchum.py @@ -21,6 +21,7 @@ from generic import PesterIcon, RightClickList, MultiTextDialog, PesterList from convo import PesterTabWindow, PesterText, PesterInput, PesterConvo from parsetools import convertTags, addTimeInitial from memos import PesterMemo, MemoTabWindow, TimeTracker +from irc import PesterIRC class waitingMessageHolder(object): def __init__(self, mainwindow, **msgfuncs): @@ -1656,6 +1657,26 @@ class PesterWindow(MovingWindow): closeSignal = QtCore.pyqtSignal() reconnectIRC = QtCore.pyqtSignal() +class IRCThread(QtCore.QThread): + def __init__(self, ircobj): + QtCore.QThread.__init__(self) + self.irc = ircobj + def run(self): + irc = self.irc + irc.IRCConnect() + while 1: + if irc.brokenConnection: + irc.brokenConnection = False + self.restartIRC.emit() + irc.closeConnection() + irc.IRCConnect() + try: + irc.updateIRC() + except socket.error: + irc.setConnectionBroken() + + restartIRC = QtCore.pyqtSignal() + class PesterTray(QtGui.QSystemTrayIcon): def __init__(self, icon, mainwindow, parent): QtGui.QSystemTrayIcon.__init__(self, icon, parent) @@ -1675,9 +1696,6 @@ class MainProgram(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) self.app = QtGui.QApplication(sys.argv) - from qt4reactor import qt4reactor - qt4reactor.install() - from irc import PesterIRC if pygame.mixer: # we could set the frequency higher but i love how cheesy it sounds try: @@ -1726,7 +1744,9 @@ class MainProgram(QtCore.QObject): self.irc = PesterIRC(self.widget.config, self.widget) self.connectWidgets(self.irc, self.widget) - self.irc.IRCConnect() + self.ircapp = IRCThread(self.irc) + self.connect(self.ircapp, QtCore.SIGNAL('restartIRC()'), + self, QtCore.SLOT('restartIRC()')) def connectWidgets(self, irc, widget): irc.connect(widget, QtCore.SIGNAL('sendMessage(QString, QString)'), @@ -1846,6 +1866,7 @@ class MainProgram(QtCore.QObject): sys.exit(0) def run(self): + self.ircapp.start() self.widget.loadingscreen = LoadingScreen(self.widget) self.connect(self.widget.loadingscreen, QtCore.SIGNAL('rejected()'), self.widget, QtCore.SLOT('close()')) diff --git a/qt4reactor/.gitignore b/qt4reactor/.gitignore deleted file mode 100644 index e1fb410..0000000 --- a/qt4reactor/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -*.[oa] -*~ -*.pyc -*.pyo -*.class -.project -.pydevproject -*.cache -_trial_temp diff --git a/qt4reactor/LICENSE b/qt4reactor/LICENSE deleted file mode 100644 index 5fc7ac7..0000000 --- a/qt4reactor/LICENSE +++ /dev/null @@ -1,57 +0,0 @@ -Copyright (c) 2001-2010 -Allen Short -Andy Gayton -Andrew Bennetts -Antoine Pitrou -Apple Computer, Inc. -Benjamin Bruheim -Bob Ippolito -Canonical Limited -Christopher Armstrong -David Reid -Donovan Preston -Eric Mangold -Eyal Lotem -Itamar Shtull-Trauring -James Knight -Jason A. Mobarak -Jean-Paul Calderone -Jessica McKellar -Jonathan Jacobs -Jonathan Lange -Jonathan D. Simms -Jürgen Hermann -Kevin Horn -Kevin Turner -Mary Gardiner -Matthew Lefkowitz -Massachusetts Institute of Technology -Moshe Zadka -Paul Swartz -Pavel Pergamenshchik -Ralph Meijer -Sean Riley -Software Freedom Conservancy -Travis B. Hartwell -Thijs Triemstra -Thomas Herve -Timothy Allen - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/qt4reactor/README b/qt4reactor/README deleted file mode 100644 index e6b5fec..0000000 --- a/qt4reactor/README +++ /dev/null @@ -1,55 +0,0 @@ -Unpack this directory into your PYTHONPATH. - -test with: - -trial --reactor=qt4 twisted (or twisted.test or twisted.test.test_internet) - -= Contributors = - -Many thanks to Darren Dale who provided the patch to fix the reactor for Qt4.4 - -= Using the Qt4Reactor = - -In your own code, BEFORE you import the reactor... - -app = QApplication(sys.argv) -import qt4reactor -qt4reactor.install() - -= Gui = - -There is a way to run trial using a gui... in bin, there is a routine -gtrial. Put that in the same directory as trial and it pops up a -trivial gui... hit the buton and it all runs the same... don't use the ---reactor option when calling gtrial... but all the other options -appear to work. This was just to make sure there wasn't anything -strange with guis which there doesn't appear to be - -If you're writing a conventional Qt application and just want twisted -as an addon, you can get that by calling reactor.runReturn() instead -of run(). This call needs to occur after your installation of of the -reactor and after QApplication.exec_() (or QCoreApplication.exec_() -whichever you are using. - -reactor.run() will also work as expected in a typical twisted application - -more docs in qt4reactor.py - -Note that if a QApplication or QCoreApplication instance isn't -constructed prior to calling reactor run, an internally owned -QCoreApplication is created and destroyed. This won't work if you call -runReturn instead of run unless you take responsibility for wacking -QCoreApplication yourself... - -However, most users want this reactor to do gui stuff so this -shouldn't be an issue. - -Performance impact of Qt has been reduced by minimizing use of -signaling which is expensive. 186s for qt4reactor vs 180s for select -for entire twisted trial suite. - --glenn - --- -Glenn H. Tarbox, PhD -glenn@tarbox.org diff --git a/qt4reactor/__init__.py b/qt4reactor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/qt4reactor/bin/gtrial b/qt4reactor/bin/gtrial deleted file mode 100755 index e0d899e..0000000 --- a/qt4reactor/bin/gtrial +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/python - -# Twisted, the Framework of Your Internet -# Copyright (c) 2001-2004 Twisted Matrix Laboratories. -# See LICENSE for details. - - - -### Twisted Preamble -# This makes sure that users don't have to set up their environment -# specially in order to run these programs from bin/. -import sys, os, string -if string.find(os.path.abspath(sys.argv[0]), os.sep+'Twisted') != -1: - sys.path.insert(0, os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir))) -if hasattr(os, "getuid") and os.getuid() != 0: - sys.path.insert(0, os.curdir) -### end of preamble - -# begin chdir armor -sys.path[:] = map(os.path.abspath, sys.path) -# end chdir armor - -from gtrial import run -run() diff --git a/qt4reactor/ghtTests/buttonStress.py b/qt4reactor/ghtTests/buttonStress.py deleted file mode 100755 index 7b4ec54..0000000 --- a/qt4reactor/ghtTests/buttonStress.py +++ /dev/null @@ -1,57 +0,0 @@ -import sys -from PySide import QtGui, QtScript -from PySide.QtCore import QTimer, SIGNAL, QObject -import qt4reactor - -app = QtGui.QApplication(sys.argv) -qt4reactor.install() - -from twisted.internet import reactor, task -from twisted.python import log -log.startLogging(sys.stdout) - -class doNothing(QObject): - def __init__(self): - self.count = 0 - self.looping=False - task.LoopingCall(self.printStat).start(1.0) - QObject.__init__(self) - - def doSomething(self): - if not self.looping: return - self.count += 1 - reactor.callLater(0.003,self.doSomething) - - def buttonClick(self): - if self.looping: - self.looping=False - log.msg('looping stopped....') - else: - self.looping=True - self.doSomething() - log.msg('looping started....') - - def printStat(self): - log.msg(' c: ' + str(self.count) + - ' st: ' + str(reactor._doSomethingCount)) - - -t=doNothing() - -engine = QtScript.QScriptEngine() - -button = QtGui.QPushButton() -scriptButton = engine.newQObject(button) -engine.globalObject().setProperty("button", scriptButton) - -app.connect(button, SIGNAL("clicked()"), t.buttonClick) - -engine.evaluate("button.text = 'Hello World!'") -engine.evaluate("button.styleSheet = 'font-style: italic'") -engine.evaluate("button.show()") - -reactor.runReturn() -app.exec_() -log.msg('fell off the bottom?...') - - diff --git a/qt4reactor/ghtTests/fakeAppButtonStress.py b/qt4reactor/ghtTests/fakeAppButtonStress.py deleted file mode 100755 index d5a2977..0000000 --- a/qt4reactor/ghtTests/fakeAppButtonStress.py +++ /dev/null @@ -1,51 +0,0 @@ -import sys -from PySide import QtGui, QtScript -from PySide.QtCore import QTimer, SIGNAL, QEventLoop -import qt4reactor - -app = QtGui.QApplication(sys.argv) - -qt4reactor.install() - -from twisted.internet import reactor, task - -class doNothing(object): - def __init__(self): - self.count = 0 - self.running=False - task.LoopingCall(self.printStat).start(1.0) - - - def buttonClick(self): - if self.running: - self.running=False - print 'CLICK: calling reactor stop...' - reactor.stop() - print 'reactor stop called....' - else: - self.running=True - print 'CLICK: entering run' - reactor.run() - print 'reactor run returned...' - - def printStat(self): - print 'tick...' - -t=doNothing() - -engine = QtScript.QScriptEngine() - -button = QtGui.QPushButton() -scriptButton = engine.newQObject(button) -engine.globalObject().setProperty("button", scriptButton) - -app.connect(button, SIGNAL("clicked()"), t.buttonClick) - -engine.evaluate("button.text = 'Hello World!'") -engine.evaluate("button.styleSheet = 'font-style: italic'") -engine.evaluate("button.show()") - -app.exec_() -print 'fell off the bottom?...' - - diff --git a/qt4reactor/ghtTests/ircClient.py b/qt4reactor/ghtTests/ircClient.py deleted file mode 100644 index 9e6fdfb..0000000 --- a/qt4reactor/ghtTests/ircClient.py +++ /dev/null @@ -1,106 +0,0 @@ -from PySide.QtCore import * -from PySide.QtGui import * -import sys, qt4reactor - -app = QApplication(sys.argv) -qt4reactor.install() - -from twisted.words.protocols import irc -from twisted.internet import reactor, protocol -from twisted.python import log - -import time, sys - -class IRCCore(irc.IRCClient): - nickname = 'dosdsdssd' - def connectionMade(self): - self.nickname = self.factory.window.nickName.text().encode('ascii') - self.factory.window.protocol = self - irc.IRCClient.connectionMade(self) - self.log('connected!!') - def connectionLost(self, reason): - self.log('disconnected... :( %s'%reason) - def signedOn(self): - chanName = self.factory.window.channelName.text().encode('ascii') - self.join(chanName) - def joined(self, channel): - self.log('joined %s'%channel) - def privmsg(self, user, channel, msg): - self.log('%s %s %s'%(user, channel, msg)) - def action(self, user, channel, msg): - self.log('action: %s %s %s'%(user, channel, msg)) - def log(self, str): - self.factory.window.view.addItem(str) - -class IRCCoreFactory(protocol.ClientFactory): - protocol = IRCCore - def __init__(self, window): - self.window = window - def clientConnectionLost(self, connector, reason): - # reconnect to server if lose connection - connector.connect() - def clientConnectionFailed(self, connector, reason): - print('connection failed! :(', reason) - reactor.stop() - -class MainWindow(QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() - connectLayout = QHBoxLayout() - connectLayout.addWidget(QLabel('Server:')) - self.serverName = QLineEdit('irc.freenode.org') - connectLayout.addWidget(self.serverName) - connectLayout.addWidget(QLabel('Channel:')) - self.channelName = QLineEdit('#pangaea') - connectLayout.addWidget(self.channelName) - connectLayout.addWidget(QLabel('Nick:')) - self.nickName = QLineEdit('ceruleanwave9832') - connectLayout.addWidget(self.nickName) - self.connectButton = QPushButton('Connect!') - connectLayout.addWidget(self.connectButton) - self.connectButton.clicked.connect(self.connectIRC) - - self.view = QListWidget() - self.entry = QLineEdit() - self.entry.returnPressed.connect(self.sendMessage) - irc = QWidget(self) - vbox = QVBoxLayout() - vbox.addLayout(connectLayout) - vbox.addWidget(self.view) - vbox.addWidget(self.entry) - irc.setLayout(vbox) - self.setCentralWidget(irc) - self.setWindowTitle('IRC') - self.setUnifiedTitleAndToolBarOnMac(True) - self.showMaximized() - - self.protocol = None - def connectIRC(self): - self.connectButton.setDisabled(True) - self.channelName.setDisabled(True) - self.nickName.setDisabled(True) - self.serverName.setDisabled(True) - ircCoreFactory = IRCCoreFactory(self) - serverName = self.serverName.text().encode('ascii') - reactor.connectTCP(serverName, 6667, ircCoreFactory) - #reactor.runReturn() - #app.exit() - #app.exit() - reactor.run() - def sendMessage(self): - if self.protocol: - chanName = self.channelName.text().encode('ascii') - message = self.entry.text().encode('ascii') - self.protocol.msg(chanName, message) - self.view.addItem('%s <%s> %s'%(chanName, self.protocol.nickname, message)) - else: - self.view.addItem('Not connected.') - self.entry.setText('') - def closeEvent(self, event): - print('Attempting to close the main window!') - reactor.stop() - event.accept() - -if __name__ == '__main__': - mainWin = MainWindow() - sys.exit(app.exec_()) diff --git a/qt4reactor/ghtTests/testIterate.py b/qt4reactor/ghtTests/testIterate.py deleted file mode 100755 index 261155b..0000000 --- a/qt4reactor/ghtTests/testIterate.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys -from PySide import QtGui, QtScript -from PySide.QtCore import QTimer, SIGNAL -import qt4reactor - -app = QtGui.QApplication(sys.argv) -qt4reactor.install() - -from twisted.internet import reactor, task -from twisted.python import log -log.startLogging(sys.stdout) - -def testReactor(): - print 'tick...' - -def buttonClick(): - print 'click...' - reactor.iterate(5.0) - print 'click return' - -engine = QtScript.QScriptEngine() - -button = QtGui.QPushButton() -scriptButton = engine.newQObject(button) -engine.globalObject().setProperty("button", scriptButton) - -app.connect(button, SIGNAL("clicked()"), buttonClick) - -engine.evaluate("button.text = 'Hello World!'") -engine.evaluate("button.styleSheet = 'font-style: italic'") -engine.evaluate("button.show()") - -task.LoopingCall(testReactor).start(1.0) -reactor.run() -log.msg('fell off the bottom?...') - - diff --git a/qt4reactor/ghtTests/trivialscript.py b/qt4reactor/ghtTests/trivialscript.py deleted file mode 100755 index 2c015cc..0000000 --- a/qt4reactor/ghtTests/trivialscript.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - -from twisted.application import reactors -import qt4reactor -qt4reactor.install() -#reactors.installReactor('qt4') - -from twisted.internet import reactor, task -from twisted.python import log -log.startLogging(sys.stdout) - -def testReactor(): - print 'tick...' - -def doit(): - task.LoopingCall(testReactor).start(1.0) - reactor.callLater(15.0,reactor.stop) - -reactor.callWhenRunning(doit) -log.msg('calling reactor.run()') -reactor.run() -log.msg('fell off the bottom?...') - -#sys.exit(app.exec_()) - diff --git a/qt4reactor/gtrial.py b/qt4reactor/gtrial.py deleted file mode 100755 index 704a2f4..0000000 --- a/qt4reactor/gtrial.py +++ /dev/null @@ -1,41 +0,0 @@ -import sys -from PyQt4 import QtGui, QtScript -from PyQt4.QtCore import QTimer, SIGNAL, QEventLoop -import qt4reactor - -app = QtGui.QApplication(sys.argv) - -qt4reactor.install() - -from twisted.internet import reactor, task - -class doNothing(object): - def __init__(self): - self.count = 0 - self.running=False - - def buttonClick(self): - if not self.running: - from twisted.scripts import trial - trial.run() - -def run(): - - t=doNothing() - - engine = QtScript.QScriptEngine() - - button = QtGui.QPushButton() - scriptButton = engine.newQObject(button) - engine.globalObject().setProperty("button", scriptButton) - - app.connect(button, SIGNAL("clicked()"), t.buttonClick) - - engine.evaluate("button.text = 'Do Twisted Gui Trial'") - engine.evaluate("button.styleSheet = 'font-style: italic'") - engine.evaluate("button.show()") - - app.exec_() - print 'fell off the bottom?...' - - diff --git a/qt4reactor/qt4reactor.py b/qt4reactor/qt4reactor.py deleted file mode 100644 index eff7e2e..0000000 --- a/qt4reactor/qt4reactor.py +++ /dev/null @@ -1,359 +0,0 @@ -# Copyright (c) 2001-2011 Twisted Matrix Laboratories. -# See LICENSE for details. - - -""" -This module provides support for Twisted to be driven by the Qt mainloop. - -In order to use this support, simply do the following:: - | app = QApplication(sys.argv) # your code to init Qt - | import qt4reactor - | qt4reactor.install() - -alternatively: - - | from twisted.application import reactors - | reactors.installReactor('qt4') - -Then use twisted.internet APIs as usual. The other methods here are not -intended to be called directly. - -If you don't instantiate a QApplication or QCoreApplication prior to -installing the reactor, a QCoreApplication will be constructed -by the reactor. QCoreApplication does not require a GUI so trial testing -can occur normally. - -Twisted can be initialized after QApplication.exec_() with a call to -reactor.runReturn(). calling reactor.stop() will unhook twisted but -leave your Qt application running - -API Stability: stable - -Maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>} - -Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} -Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>} -Subsequent port by therve -""" - -import sys -import time -from zope.interface import implements -from twisted.internet.interfaces import IReactorFDSet -from twisted.python import log, runtime -from twisted.internet import posixbase -from twisted.python.runtime import platformType, platform - -try: - from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication - from PyQt4.QtCore import QEventLoop -except ImportError: - from PySide.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication - from PySide.QtCore import QEventLoop - - -class TwistedSocketNotifier(QObject): - """ - Connection between an fd event and reader/writer callbacks. - """ - - def __init__(self, parent, reactor, watcher, socketType): - QObject.__init__(self, parent) - self.reactor = reactor - self.watcher = watcher - fd = watcher.fileno() - self.notifier = QSocketNotifier(fd, socketType, parent) - self.notifier.setEnabled(True) - if socketType == QSocketNotifier.Read: - self.fn = self.read - else: - self.fn = self.write - QObject.connect(self.notifier, SIGNAL("activated(int)"), self.fn) - - - def shutdown(self): - self.notifier.setEnabled(False) - self.disconnect(self.notifier, SIGNAL("activated(int)"), self.fn) - self.fn = self.watcher = None - self.notifier.deleteLater() - self.deleteLater() - - - def read(self, fd): - if not self.watcher: - return - w = self.watcher - # doRead can cause self.shutdown to be called so keep a reference to self.watcher - def _read(): - #Don't call me again, until the data has been read - self.notifier.setEnabled(False) - why = None - try: - why = w.doRead() - inRead = True - except: - inRead = False - log.err() - why = sys.exc_info()[1] - if why: - self.reactor._disconnectSelectable(w, why, inRead) - elif self.watcher: - self.notifier.setEnabled(True) # Re enable notification following sucessfull read - self.reactor._iterate(fromqt=True) - log.callWithLogger(w, _read) - - def write(self, sock): - if not self.watcher: - return - w = self.watcher - def _write(): - why = None - self.notifier.setEnabled(False) - - try: - why = w.doWrite() - except: - log.err() - why = sys.exc_info()[1] - if why: - self.reactor._disconnectSelectable(w, why, False) - elif self.watcher: - self.notifier.setEnabled(True) - self.reactor._iterate(fromqt=True) - log.callWithLogger(w, _write) - - - -class QtReactor(posixbase.PosixReactorBase): - implements(IReactorFDSet) - - def __init__(self): - self._reads = {} - self._writes = {} - self._notifiers = {} - self._timer = QTimer() - self._timer.setSingleShot(True) - QObject.connect(self._timer, SIGNAL("timeout()"), self.iterate) - - if QCoreApplication.startingUp(): - # Application Object has not been started yet - self.qApp=QCoreApplication([]) - self._ownApp=True - else: - self.qApp = QCoreApplication.instance() - self._ownApp=False - self._blockApp = None - posixbase.PosixReactorBase.__init__(self) - - - def _add(self, xer, primary, type): - """ - Private method for adding a descriptor from the event loop. - - It takes care of adding it if new or modifying it if already added - for another state (read -> read/write for example). - """ - if xer not in primary: - primary[xer] = TwistedSocketNotifier(None, self, xer, type) - - - def addReader(self, reader): - """ - Add a FileDescriptor for notification of data available to read. - """ - self._add(reader, self._reads, QSocketNotifier.Read) - - - def addWriter(self, writer): - """ - Add a FileDescriptor for notification of data available to write. - """ - self._add(writer, self._writes, QSocketNotifier.Write) - - - def _remove(self, xer, primary): - """ - Private method for removing a descriptor from the event loop. - - It does the inverse job of _add, and also add a check in case of the fd - has gone away. - """ - if xer in primary: - notifier = primary.pop(xer) - notifier.shutdown() - - - def removeReader(self, reader): - """ - Remove a Selectable for notification of data available to read. - """ - self._remove(reader, self._reads) - - - def removeWriter(self, writer): - """ - Remove a Selectable for notification of data available to write. - """ - self._remove(writer, self._writes) - - - def removeAll(self): - """ - Remove all selectables, and return a list of them. - """ - rv = self._removeAll(self._reads, self._writes) - return rv - - - def getReaders(self): - return self._reads.keys() - - - def getWriters(self): - return self._writes.keys() - - - def callLater(self,howlong, *args, **kargs): - rval = super(QtReactor,self).callLater(howlong, *args, **kargs) - self.reactorInvocation() - return rval - - - def reactorInvocation(self): - self._timer.stop() - self._timer.setInterval(0) - self._timer.start() - - - def _iterate(self, delay=None, fromqt=False): - """See twisted.internet.interfaces.IReactorCore.iterate. - """ - self.runUntilCurrent() - self.doIteration(delay, fromqt) - - iterate = _iterate - - def doIteration(self, delay=None, fromqt=False): - 'This method is called by a Qt timer or by network activity on a file descriptor' - - if not self.running and self._blockApp: - self._blockApp.quit() - self._timer.stop() - delay = max(delay, 1) - if not fromqt: - self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000) - if self.timeout() is None: - timeout = 0.1 - elif self.timeout() == 0: - timeout = 0 - else: - timeout = self.timeout() - self._timer.setInterval(timeout * 1000) - self._timer.start() - - - def runReturn(self, installSignalHandlers=True): - self.startRunning(installSignalHandlers=installSignalHandlers) - self.reactorInvocation() - - - def run(self, installSignalHandlers=True): - if self._ownApp: - self._blockApp = self.qApp - else: - self._blockApp = QEventLoop() - self.runReturn() - self._blockApp.exec_() - - -class QtEventReactor(QtReactor): - def __init__(self, *args, **kwargs): - self._events = {} - super(QtEventReactor, self).__init__() - - - def addEvent(self, event, fd, action): - """ - Add a new win32 event to the event loop. - """ - self._events[event] = (fd, action) - - - def removeEvent(self, event): - """ - Remove an event. - """ - if event in self._events: - del self._events[event] - - - def doEvents(self): - handles = self._events.keys() - if len(handles) > 0: - val = None - while val != WAIT_TIMEOUT: - val = MsgWaitForMultipleObjects(handles, 0, 0, QS_ALLINPUT | QS_ALLEVENTS) - if val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles): - event_id = handles[val - WAIT_OBJECT_0] - if event_id in self._events: - fd, action = self._events[event_id] - log.callWithLogger(fd, self._runAction, action, fd) - elif val == WAIT_TIMEOUT: - pass - else: - #print 'Got an unexpected return of %r' % val - return - - - def _runAction(self, action, fd): - try: - closed = getattr(fd, action)() - except: - closed = sys.exc_info()[1] - log.deferr() - - if closed: - self._disconnectSelectable(fd, closed, action == 'doRead') - - - def timeout(self): - t = super(QtEventReactor, self).timeout() - return min(t, 0.01) - - - def iterate(self, delay=None): - """See twisted.internet.interfaces.IReactorCore.iterate. - """ - self.runUntilCurrent() - self.doEvents() - self.doIteration(delay) - - -def posixinstall(): - """ - Install the Qt reactor. - """ - p = QtReactor() - from twisted.internet.main import installReactor - installReactor(p) - - -def win32install(): - """ - Install the Qt reactor. - """ - p = QtEventReactor() - from twisted.internet.main import installReactor - installReactor(p) - - -if runtime.platform.getType() == 'win32': - from win32event import CreateEvent, MsgWaitForMultipleObjects - from win32event import WAIT_OBJECT_0, WAIT_TIMEOUT, QS_ALLINPUT, QS_ALLEVENTS - install = win32install -else: - install = posixinstall - - -__all__ = ["install"] - diff --git a/qt4reactor/twisted/plugins/qt4.py b/qt4reactor/twisted/plugins/qt4.py deleted file mode 100644 index 0f380b5..0000000 --- a/qt4reactor/twisted/plugins/qt4.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2001-2010 Twisted Matrix Laboratories. -# see LICENSE for details - - -from twisted.application.reactors import Reactor - -qt4 = Reactor('qt4', 'qt4reactor', 'Qt4 integration reactor') -qt4bad = Reactor('qt4bad', 'qt4reactor_bad', 'Qt4 broken reactor') -