Python 3 port:

/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time

This patch port backend, bridge and frontends to Python 3.

Roughly this has been done this way:

- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
  to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
This commit is contained in:
Goffi 2019-08-13 19:08:41 +02:00
commit fbfe96c505

View file

@ -43,12 +43,12 @@ ROOM_USER_MOVED = (ROOM_USER_JOINED, ROOM_USER_LEFT)
try:
# FIXME: to be removed when an acceptable solution is here
unicode("") # XXX: unicode doesn't exist in pyjamas
str("") # XXX: unicode doesn't exist in pyjamas
except (
TypeError,
AttributeError,
): # Error raised is not the same depending on pyjsbuild options
unicode = str
str = str
# FIXME: day_format need to be settable (i18n)
@ -78,23 +78,20 @@ class Message(object):
)
# is user mentioned here ?
if self.parent.type == C.CHAT_GROUP and not self.own_mess:
for m in msg.itervalues():
for m in msg.values():
if self.parent.nick.lower() in m.lower():
self._mention = True
break
self.handleMe()
self.widgets = set() # widgets linked to this message
def __unicode__(self):
return u"Message<{mess_type}> [{time}]{nick}> {message}".format(
def __str__(self):
return "Message<{mess_type}> [{time}]{nick}> {message}".format(
mess_type=self.type,
time=self.time_text,
nick=self.nick,
message=self.main_message)
def __str__(self):
return self.__unicode__().encode('utf-8', 'ignore')
@property
def host(self):
return self.parent.host
@ -126,28 +123,28 @@ class Message(object):
return self.message[""]
except KeyError:
try:
lang, mess = self.message.iteritems().next()
lang, mess = next(iter(self.message.items()))
self.selected_lang = lang
return mess
except StopIteration:
log.error(u"Can't find message for uid {}".format(self.uid))
log.error("Can't find message for uid {}".format(self.uid))
return ""
@property
def main_message_xhtml(self):
"""rich message"""
xhtml = {k: v for k, v in self.extra.iteritems() if "html" in k}
xhtml = {k: v for k, v in self.extra.items() if "html" in k}
if xhtml:
# FIXME: we only return first found value for now
return next(xhtml.itervalues())
return next(iter(xhtml.values()))
@property
def time_text(self):
"""Return timestamp in a nicely formatted way"""
# if the message was sent before today, we print the full date
timestamp = time.localtime(self.timestamp)
time_format = u"%c" if timestamp < self.parent.day_change else u"%H:%M"
return time.strftime(time_format, timestamp).decode(getlocale()[1] or "utf-8")
time_format = "%c" if timestamp < self.parent.day_change else "%H:%M"
return time.strftime(time_format, timestamp)
@property
def avatar(self):
@ -162,7 +159,7 @@ class Message(object):
try:
return self.extra["user_nick"]
except KeyError:
log.error(u"extra data is missing user nick for uid {}".format(self.uid))
log.error("extra data is missing user nick for uid {}".format(self.uid))
return ""
# FIXME: converted getSpecials to list for pyjamas
if self.parent.type == C.CHAT_GROUP or entity in list(
@ -197,8 +194,8 @@ class Message(object):
# TODO: XHTML-IM /me are not handled
me = False
# we need to check /me for every message
for m in self.message.itervalues():
if m.startswith(u"/me "):
for m in self.message.values():
if m.startswith("/me "):
me = True
else:
me = False
@ -207,8 +204,8 @@ class Message(object):
self.type = C.MESS_TYPE_INFO
self.extra["info_type"] = "me"
nick = self.nick
for lang, mess in self.message.iteritems():
self.message[lang] = u"* " + nick + mess[3:]
for lang, mess in self.message.items():
self.message[lang] = "* " + nick + mess[3:]
class MessageWidget(object):
@ -245,7 +242,7 @@ class Occupant(object):
@property
def jid(self):
"""jid in the room"""
return jid.JID(u"{}/{}".format(self.parent.target.bare, self.nick))
return jid.JID("{}/{}".format(self.parent.target.bare, self.nick))
@property
def real_jid(self):
@ -295,10 +292,10 @@ class QuickChat(quick_widgets.QuickWidget):
if type_ == C.CHAT_GROUP:
if target.resource:
raise exceptions.InternalError(
u"a group chat entity can't have a resource"
"a group chat entity can't have a resource"
)
if nick is None:
raise exceptions.InternalError(u"nick must not be None for group chat")
raise exceptions.InternalError("nick must not be None for group chat")
self.nick = nick
self.occupants = {}
@ -306,7 +303,7 @@ class QuickChat(quick_widgets.QuickWidget):
else:
if occupants is not None or nick is not None:
raise exceptions.InternalError(
u"only group chat can have occupants or nick"
"only group chat can have occupants or nick"
)
self.messages = OrderedDict() # key: uid, value: Message instance
self.games = {} # key=game name (unicode), value=instance of quick_games.RoomGame
@ -334,25 +331,25 @@ class QuickChat(quick_widgets.QuickWidget):
# FIXME: we don't use getter/setter here because of pyjamas
# TODO: use proper getter/setter once we get rid of pyjamas
if self._locked:
log.warning(u"{wid} is already locked!".format(wid=self))
log.warning("{wid} is already locked!".format(wid=self))
return
self._locked = True
# messageNew signals are cached when locked
self._cache = OrderedDict()
log.debug(u"{wid} is now locked".format(wid=self))
log.debug("{wid} is now locked".format(wid=self))
def setUnlocked(self):
if not self._locked:
log.debug(u"{wid} was already unlocked".format(wid=self))
log.debug("{wid} was already unlocked".format(wid=self))
return
self._locked = False
for uid, data in self._cache.iteritems():
for uid, data in self._cache.items():
if uid not in self.messages:
self.messageNew(*data)
else:
log.debug(u"discarding message already in history: {data}, ".format(data=data))
log.debug("discarding message already in history: {data}, ".format(data=data))
del self._cache
log.debug(u"{wid} is now unlocked".format(wid=self))
log.debug("{wid} is now unlocked".format(wid=self))
def postInit(self):
"""Method to be called by frontend after widget is initialised
@ -404,8 +401,8 @@ class QuickChat(quick_widgets.QuickWidget):
if self._resync_lock:
return
self._resync_lock = True
log.debug(u"resynchronising {self}".format(self=self))
for mess in reversed(self.messages.values()):
log.debug("resynchronising {self}".format(self=self))
for mess in reversed(list(self.messages.values())):
if mess.type == C.MESS_TYPE_INFO:
continue
last_message = mess
@ -417,7 +414,7 @@ class QuickChat(quick_widgets.QuickWidget):
if self.type == C.CHAT_GROUP:
self.occupantsClear()
self.host.bridge.mucOccupantsGet(
unicode(self.target), self.profile, callback=self.updateOccupants,
str(self.target), self.profile, callback=self.updateOccupants,
errback=log.error)
self.historyPrint(
size=C.HISTORY_LIMIT_NONE,
@ -427,15 +424,15 @@ class QuickChat(quick_widgets.QuickWidget):
## Widget management ##
def __unicode__(self):
return u"Chat Widget [target: {}, type: {}, profile: {}]".format(
def __str__(self):
return "Chat Widget [target: {}, type: {}, profile: {}]".format(
self.target, self.type, self.profile
)
@staticmethod
def getWidgetHash(target, profiles):
profile = list(profiles)[0]
return profile + "\n" + unicode(target.bare)
return profile + "\n" + str(target.bare)
@staticmethod
def getPrivateHash(target, profile):
@ -443,7 +440,7 @@ class QuickChat(quick_widgets.QuickWidget):
This method should be used with force_hash to get unique widget for private MUC conversations
"""
return (unicode(profile), target)
return (str(profile), target)
def addTarget(self, target):
super(QuickChat, self).addTarget(target)
@ -456,7 +453,7 @@ class QuickChat(quick_widgets.QuickWidget):
"""copy important attribute for a new widget"""
kwargs["type_"] = self.type
if self.type == C.CHAT_GROUP:
kwargs["occupants"] = {o.nick: o.data for o in self.occupants.itervalues()}
kwargs["occupants"] = {o.nick: o.data for o in self.occupants.values()}
kwargs["subject"] = self.subject
try:
kwargs["nick"] = self.nick
@ -492,7 +489,7 @@ class QuickChat(quick_widgets.QuickWidget):
def setOccupants(self, occupants):
"""Set the whole list of occupants"""
assert len(self.occupants) == 0
for nick, data in occupants.iteritems():
for nick, data in occupants.items():
# XXX: this log is disabled because it's really too verbose
# but kept commented as it may be useful for debugging
# log.debug(u"adding occupant {nick} to {room}".format(
@ -510,12 +507,12 @@ class QuickChat(quick_widgets.QuickWidget):
updated_occupants = set(occupants)
left_occupants = local_occupants - updated_occupants
joined_occupants = updated_occupants - local_occupants
log.debug(u"updating occupants for {room}:\n"
u"left: {left_occupants}\n"
u"joined: {joined_occupants}"
log.debug("updating occupants for {room}:\n"
"left: {left_occupants}\n"
"joined: {joined_occupants}"
.format(room=self.target,
left_occupants=u", ".join(left_occupants),
joined_occupants=u", ".join(joined_occupants)))
left_occupants=", ".join(left_occupants),
joined_occupants=", ".join(joined_occupants)))
for nick in left_occupants:
self.removeUser(occupants[nick])
for nick in joined_occupants:
@ -533,7 +530,7 @@ class QuickChat(quick_widgets.QuickWidget):
try:
occupant = self.occupants.pop(nick)
except KeyError:
log.warning(u"Trying to remove an unknown occupant: {}".format(nick))
log.warning("Trying to remove an unknown occupant: {}".format(nick))
else:
return occupant
@ -602,12 +599,12 @@ class QuickChat(quick_widgets.QuickWidget):
if filters is None:
filters = {}
if size == 0:
log.debug(u"Empty history requested, skipping")
log.debug("Empty history requested, skipping")
self._onHistoryPrinted()
return
log_msg = _(u"now we print the history")
log_msg = _("now we print the history")
if size != C.HISTORY_LIMIT_DEFAULT:
log_msg += _(u" ({} messages)".format(size))
log_msg += _(" ({} messages)".format(size))
log.debug(log_msg)
if self.type == C.CHAT_ONE2ONE:
@ -661,17 +658,17 @@ class QuickChat(quick_widgets.QuickWidget):
callback()
def _historyGetEb(err):
log.error(_(u"Can't get history: {}").format(err))
log.error(_("Can't get history: {}").format(err))
self._onHistoryPrinted()
if callback is not None:
callback()
self.host.bridge.historyGet(
unicode(self.host.profiles[profile].whoami.bare),
unicode(target),
str(self.host.profiles[profile].whoami.bare),
str(target),
size,
True,
{k: unicode(v) for k,v in filters.iteritems()},
{k: str(v) for k,v in filters.items()},
profile,
callback=_historyGetCb,
errback=_historyGetEb,
@ -683,7 +680,7 @@ class QuickChat(quick_widgets.QuickWidget):
self.messageEncryptionStarted(session_data)
def messageEncryptionGetEb(self, failure_):
log.error(_(u"Can't get encryption state: {reason}").format(reason=failure_))
log.error(_("Can't get encryption state: {reason}").format(reason=failure_))
def getEncryptionState(self):
"""Retrieve encryption state with current target.
@ -693,7 +690,7 @@ class QuickChat(quick_widgets.QuickWidget):
"""
if self.type == C.CHAT_GROUP:
return
self.host.bridge.messageEncryptionGet(unicode(self.target.bare), self.profile,
self.host.bridge.messageEncryptionGet(str(self.target.bare), self.profile,
callback=self.messageEncryptionGetCb,
errback=self.messageEncryptionGetEb)
@ -715,7 +712,7 @@ class QuickChat(quick_widgets.QuickWidget):
return
if not msg and not subject and type_ != C.MESS_TYPE_INFO:
log.warning(u"Received an empty message for uid {}".format(uid))
log.warning("Received an empty message for uid {}".format(uid))
return
if self.type == C.CHAT_GROUP:
@ -734,7 +731,7 @@ class QuickChat(quick_widgets.QuickWidget):
pass
else:
user_data = {
k[5:]: v for k, v in extra.iteritems() if k.startswith("user_")
k[5:]: v for k, v in extra.items() if k.startswith("user_")
}
if info_type == ROOM_USER_JOINED:
self.addUser(user_data)
@ -747,18 +744,18 @@ class QuickChat(quick_widgets.QuickWidget):
self.messages[uid] = message
if "received_timestamp" in extra:
log.warning(u"Delayed message received after history, this should not happen")
log.warning("Delayed message received after history, this should not happen")
self.createMessage(message)
def messageEncryptionStarted(self, session_data):
self.encrypted = True
log.debug(_(u"message encryption started with {target} using {encryption}").format(
target=self.target, encryption=session_data[u'name']))
log.debug(_("message encryption started with {target} using {encryption}").format(
target=self.target, encryption=session_data['name']))
def messageEncryptionStopped(self, session_data):
self.encrypted = False
log.debug(_(u"message encryption stopped with {target} (was using {encryption})")
.format(target=self.target, encryption=session_data[u'name']))
log.debug(_("message encryption stopped with {target} (was using {encryption})")
.format(target=self.target, encryption=session_data['name']))
def createMessage(self, message, append=False):
"""Must be implemented by frontend to create and show a new message widget
@ -810,12 +807,12 @@ class QuickChat(quick_widgets.QuickWidget):
count = wid.reentered_count = 1
nick = wid.mess_data.nick
if message.info_type == ROOM_USER_LEFT:
wid.message = _(u"<= {nick} has left the room ({count})").format(
wid.message = _("<= {nick} has left the room ({count})").format(
nick=nick, count=count
)
else:
wid.message = _(
u"<=> {nick} re-entered the room ({count})"
"<=> {nick} re-entered the room ({count})"
).format(nick=nick, count=count)
wid.reentered_count += 1
return True
@ -845,7 +842,7 @@ class QuickChat(quick_widgets.QuickWidget):
This change the subject on the room itself (i.e. via XMPP),
while setSubject change the subject of this widget
"""
self.host.bridge.mucSubject(unicode(self.target), new_subject, self.profile)
self.host.bridge.mucSubject(str(self.target), new_subject, self.profile)
def addGamePanel(self, widget):
"""Insert a game panel to this Chat dialog.
@ -879,7 +876,7 @@ class QuickChat(quick_widgets.QuickWidget):
self.occupants[nick].state = state
except KeyError:
log.warning(
u"{nick} not found in {room}, ignoring new chat state".format(
"{nick} not found in {room}, ignoring new chat state".format(
nick=nick, room=self.target.bare
)
)
@ -902,7 +899,7 @@ class QuickChat(quick_widgets.QuickWidget):
# entity is not here anymore
pass
for m in self.messages.values():
for m in list(self.messages.values()):
if m.nick == entity.resource:
for w in m.widgets:
w.update({"avatar": filename})
@ -911,8 +908,8 @@ class QuickChat(quick_widgets.QuickWidget):
entity.bare == self.target.bare
or entity.bare == self.host.profiles[profile].whoami.bare
):
log.info(u"avatar updated for {}".format(entity))
for m in self.messages.values():
log.info("avatar updated for {}".format(entity))
for m in list(self.messages.values()):
if m.from_jid.bare == entity.bare:
for w in m.widgets:
w.update({"avatar": filename})