diff sat/memory/memory.py @ 3028:ab2696e34d29

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
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 94708a7d3ecf
children 98d1f34ce5b9
line wrap: on
line diff
--- a/sat/memory/memory.py	Wed Jul 31 11:31:22 2019 +0200
+++ b/sat/memory/memory.py	Tue Aug 13 19:08:41 2019 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # SAT: a jabber client
@@ -26,7 +26,7 @@
 import os.path
 import copy
 from collections import namedtuple
-from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
+from configparser import SafeConfigParser, NoOptionError, NoSectionError
 from uuid import uuid4
 from twisted.python import failure
 from twisted.internet import defer, reactor, error
@@ -76,7 +76,7 @@
             session_id = str(uuid4())
         elif session_id in self._sessions:
             raise exceptions.ConflictError(
-                u"Session id {} is already used".format(session_id)
+                "Session id {} is already used".format(session_id)
             )
         timer = reactor.callLater(self.timeout, self._purgeSession, session_id)
         if session_data is None:
@@ -99,9 +99,9 @@
             pass
         del self._sessions[session_id]
         log.debug(
-            u"Session {} purged{}".format(
+            "Session {} purged{}".format(
                 session_id,
-                u" (profile {})".format(profile) if profile is not None else u"",
+                " (profile {})".format(profile) if profile is not None else "",
             )
         )
 
@@ -147,10 +147,10 @@
         self._purgeSession(session_id)
 
     def keys(self):
-        return self._sessions.keys()
+        return list(self._sessions.keys())
 
     def iterkeys(self):
-        return self._sessions.iterkeys()
+        return iter(self._sessions.keys())
 
 
 class ProfileSessions(Sessions):
@@ -165,7 +165,7 @@
         @return: a list containing the sessions ids
         """
         ret = []
-        for session_id in self._sessions.iterkeys():
+        for session_id in self._sessions.keys():
             try:
                 timer, session_data, profile_set = self._sessions[session_id]
             except ValueError:
@@ -245,7 +245,7 @@
         if not silent:
             log.warning(
                 _(
-                    u"A database has been found in the default local_dir for previous versions (< 0.5)"
+                    "A database has been found in the default local_dir for previous versions (< 0.5)"
                 )
             )
         tools_config.fixConfigOption("", "local_dir", old_default, silent)
@@ -306,10 +306,10 @@
         if os.path.exists(filename):
             try:
                 self.params.load_xml(filename)
-                log.debug(_(u"Parameters loaded from file: %s") % filename)
+                log.debug(_("Parameters loaded from file: %s") % filename)
                 return True
             except Exception as e:
-                log.error(_(u"Can't load parameters from file: %s") % e)
+                log.error(_("Can't load parameters from file: %s") % e)
         return False
 
     def save_xml(self, filename):
@@ -324,10 +324,10 @@
         filename = os.path.expanduser(filename)
         try:
             self.params.save_xml(filename)
-            log.debug(_(u"Parameters saved to file: %s") % filename)
+            log.debug(_("Parameters saved to file: %s") % filename)
             return True
         except Exception as e:
-            log.error(_(u"Can't save parameters to file: %s") % e)
+            log.error(_("Can't save parameters to file: %s") % e)
         return False
 
     def load(self):
@@ -356,7 +356,7 @@
         def createSession(__):
             """Called once params are loaded."""
             self._entities_cache[profile] = {}
-            log.info(u"[{}] Profile session started".format(profile))
+            log.info("[{}] Profile session started".format(profile))
             return False
 
         def backendInitialised(__):
@@ -392,13 +392,13 @@
         @param profile: %(doc_profile)s
         """
         if self.host.isConnected(profile):
-            log.debug(u"Disconnecting profile because of session stop")
+            log.debug("Disconnecting profile because of session stop")
             self.host.disconnect(profile)
         self.auth_sessions.profileDelUnique(profile)
         try:
             self._entities_cache[profile]
         except KeyError:
-            log.warning(u"Profile was not in cache")
+            log.warning("Profile was not in cache")
 
     def _isSessionStarted(self, profile_key):
         return self.isSessionStarted(self.getProfileName(profile_key))
@@ -428,10 +428,10 @@
 
         def check_result(result):
             if not result:
-                log.warning(u"Authentication failure of profile {}".format(profile))
+                log.warning("Authentication failure of profile {}".format(profile))
                 raise failure.Failure(
                     exceptions.PasswordError(
-                        u"The provided profile password doesn't match."
+                        "The provided profile password doesn't match."
                     )
                 )
             if (
@@ -460,7 +460,7 @@
             self.auth_sessions.newSession(
                 {C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile
             )
-            log.debug(u"auth session created for profile %s" % profile)
+            log.debug("auth session created for profile %s" % profile)
 
         d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
         d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]))
@@ -476,7 +476,7 @@
         except KeyError:
             log.error(
                 _(
-                    u"Trying to purge roster status cache for a profile not in memory: [%s]"
+                    "Trying to purge roster status cache for a profile not in memory: [%s]"
                 )
                 % profile
             )
@@ -489,7 +489,7 @@
         @return (list[unicode]): selected profiles
         """
         if not clients and not components:
-            log.warning(_(u"requesting no profiles at all"))
+            log.warning(_("requesting no profiles at all"))
             return []
         profiles = self.storage.getProfilesList()
         if clients and components:
@@ -533,20 +533,20 @@
         @raise exceptions.NotFound: component is not a known plugin import name
         """
         if not name:
-            raise ValueError(u"Empty profile name")
+            raise ValueError("Empty profile name")
         if name[0] == "@":
-            raise ValueError(u"A profile name can't start with a '@'")
+            raise ValueError("A profile name can't start with a '@'")
         if "\n" in name:
-            raise ValueError(u"A profile name can't contain line feed ('\\n')")
+            raise ValueError("A profile name can't contain line feed ('\\n')")
 
         if name in self._entities_cache:
-            raise exceptions.ConflictError(u"A session for this profile exists")
+            raise exceptions.ConflictError("A session for this profile exists")
 
         if component:
             if not component in self.host.plugins:
                 raise exceptions.NotFound(
                     _(
-                        u"Can't find component {component} entry point".format(
+                        "Can't find component {component} entry point".format(
                             component=component
                         )
                     )
@@ -664,7 +664,7 @@
 
     def _getPresenceStatuses(self, profile_key):
         ret = self.getPresenceStatuses(profile_key)
-        return {entity.full(): data for entity, data in ret.iteritems()}
+        return {entity.full(): data for entity, data in ret.items()}
 
     def getPresenceStatuses(self, profile_key):
         """Get all the presence statuses of a profile
@@ -676,8 +676,8 @@
         profile_cache = self._getProfileCache(client)
         entities_presence = {}
 
-        for entity_jid, entity_data in profile_cache.iteritems():
-            for resource, resource_data in entity_data.iteritems():
+        for entity_jid, entity_data in profile_cache.items():
+            for resource, resource_data in entity_data.items():
                 full_jid = copy.copy(entity_jid)
                 full_jid.resource = resource
                 try:
@@ -736,7 +736,7 @@
             entity_data = profile_cache[entity_jid.userhostJID()]
         except KeyError:
             raise exceptions.UnknownEntityError(
-                u"Entity {} not in cache".format(entity_jid)
+                "Entity {} not in cache".format(entity_jid)
             )
         resources = set(entity_data.keys())
         resources.discard(None)
@@ -758,7 +758,7 @@
             try:
                 presence_data = self.getEntityDatum(full_jid, "presence", client.profile)
             except KeyError:
-                log.debug(u"Can't get presence data for {}".format(full_jid))
+                log.debug("Can't get presence data for {}".format(full_jid))
             else:
                 if presence_data.show != C.PRESENCE_UNAVAILABLE:
                     available.append(resource)
@@ -787,7 +787,7 @@
         try:
             resources = self.getAllResources(client, entity_jid)
         except exceptions.UnknownEntityError:
-            log.warning(u"Entity is not in cache, we can't find any resource")
+            log.warning("Entity is not in cache, we can't find any resource")
             return None
         priority_resources = []
         for resource in resources:
@@ -796,13 +796,13 @@
             try:
                 presence_data = self.getEntityDatum(full_jid, "presence", client.profile)
             except KeyError:
-                log.debug(u"No presence information for {}".format(full_jid))
+                log.debug("No presence information for {}".format(full_jid))
                 continue
             priority_resources.append((resource, presence_data.priority))
         try:
             return max(priority_resources, key=lambda res_tuple: res_tuple[1])[0]
         except ValueError:
-            log.warning(u"No resource found at all for {}".format(entity_jid))
+            log.warning("No resource found at all for {}".format(entity_jid))
             return None
 
     ## Entities data ##
@@ -835,8 +835,8 @@
         """
         profile_cache = self._getProfileCache(client)
         # we construct a list of all known full jids (bare jid of entities x resources)
-        for bare_jid, entity_data in profile_cache.iteritems():
-            for resource in entity_data.iterkeys():
+        for bare_jid, entity_data in profile_cache.items():
+            for resource in entity_data.keys():
                 if resource is None:
                     continue
                 full_jid = copy.copy(bare_jid)
@@ -871,9 +871,9 @@
 
             entity_data[key] = value
             if key in self._key_signals and not silent:
-                if not isinstance(value, basestring):
+                if not isinstance(value, str):
                     log.error(
-                        u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(
+                        "Setting a non string value ({}) for a key ({}) which has a signal flag".format(
                             value, key
                         )
                     )
@@ -905,7 +905,7 @@
                 entity_data = profile_cache[jid_.userhostJID()][jid_.resource]
             except KeyError:
                 raise exceptions.UnknownEntityError(
-                    u"Entity {} not in cache".format(jid_)
+                    "Entity {} not in cache".format(jid_)
                 )
             try:
                 del entity_data[key]
@@ -919,7 +919,7 @@
         ret = self.getEntitiesData(
             [jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key
         )
-        return {jid_.full(): data for jid_, data in ret.iteritems()}
+        return {jid_.full(): data for jid_, data in ret.items()}
 
     def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE):
         """Get a list of cached values for several entities at once
@@ -961,8 +961,8 @@
                     continue
                 ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list)
         else:
-            for bare_jid, data in profile_cache.iteritems():
-                for resource, entity_cache_data in data.iteritems():
+            for bare_jid, data in profile_cache.items():
+                for resource, entity_cache_data in data.items():
                     full_jid = copy.copy(bare_jid)
                     full_jid.resource = resource
                     ret_data[full_jid] = fillEntityData(entity_cache_data)
@@ -987,7 +987,7 @@
             entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource]
         except KeyError:
             raise exceptions.UnknownEntityError(
-                u"Entity {} not in cache (was requesting {})".format(
+                "Entity {} not in cache (was requesting {})".format(
                     entity_jid, keys_list
                 )
             )
@@ -1030,14 +1030,14 @@
                 del profile_cache[entity_jid]
             except KeyError:
                 raise exceptions.UnknownEntityError(
-                    u"Entity {} not in cache".format(entity_jid)
+                    "Entity {} not in cache".format(entity_jid)
                 )
         else:
             try:
                 del profile_cache[entity_jid.userhostJID()][entity_jid.resource]
             except KeyError:
                 raise exceptions.UnknownEntityError(
-                    u"Entity {} not in cache".format(entity_jid)
+                    "Entity {} not in cache".format(entity_jid)
                 )
 
     ## Encryption ##
@@ -1103,7 +1103,7 @@
 
         def done(__):
             log.debug(
-                _(u"Personal data (%(ns)s, %(key)s) has been successfuly encrypted")
+                _("Personal data (%(ns)s, %(key)s) has been successfuly encrypted")
                 % {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key}
             )
 
@@ -1225,21 +1225,21 @@
             # the owner has all rights
             return
         if not C.ACCESS_PERMS.issuperset(perms_to_check):
-            raise exceptions.InternalError(_(u"invalid permission"))
+            raise exceptions.InternalError(_("invalid permission"))
 
         for perm in perms_to_check:
             # we check each perm and raise PermissionError as soon as one condition is not valid
             # we must never return here, we only return after the loop if nothing was blocking the access
             try:
-                perm_data = file_data[u"access"][perm]
-                perm_type = perm_data[u"type"]
+                perm_data = file_data["access"][perm]
+                perm_type = perm_data["type"]
             except KeyError:
                 raise exceptions.PermissionError()
             if perm_type == C.ACCESS_TYPE_PUBLIC:
                 continue
             elif perm_type == C.ACCESS_TYPE_WHITELIST:
                 try:
-                    jids = perm_data[u"jids"]
+                    jids = perm_data["jids"]
                 except KeyError:
                     raise exceptions.PermissionError()
                 if peer_jid.full() in jids:
@@ -1248,7 +1248,7 @@
                     raise exceptions.PermissionError()
             else:
                 raise exceptions.InternalError(
-                    _(u"unknown access type: {type}").format(type=perm_type)
+                    _("unknown access type: {type}").format(type=perm_type)
                 )
 
     @defer.inlineCallbacks
@@ -1257,7 +1257,7 @@
         current = file_data
         while True:
             self.checkFilePermission(current, peer_jid, perms_to_check)
-            parent = current[u"parent"]
+            parent = current["parent"]
             if not parent:
                 break
             files_data = yield self.getFile(
@@ -1266,7 +1266,7 @@
             try:
                 current = files_data[0]
             except IndexError:
-                raise exceptions.DataError(u"Missing parent")
+                raise exceptions.DataError("Missing parent")
 
     @defer.inlineCallbacks
     def _getParentDir(
@@ -1283,15 +1283,15 @@
         # if path is set, we have to retrieve parent directory of the file(s) from it
         if parent is not None:
             raise exceptions.ConflictError(
-                _(u"You can't use path and parent at the same time")
+                _("You can't use path and parent at the same time")
             )
-        path_elts = filter(None, path.split(u"/"))
-        if {u"..", u"."}.intersection(path_elts):
-            raise ValueError(_(u'".." or "." can\'t be used in path'))
+        path_elts = [_f for _f in path.split("/") if _f]
+        if {"..", "."}.intersection(path_elts):
+            raise ValueError(_('".." or "." can\'t be used in path'))
 
         # we retrieve all directories from path until we get the parent container
         # non existing directories will be created
-        parent = u""
+        parent = ""
         for idx, path_elt in enumerate(path_elts):
             directories = yield self.storage.getFiles(
                 client,
@@ -1306,12 +1306,12 @@
                 # from this point, directories don't exist anymore, we have to create them
             elif len(directories) > 1:
                 raise exceptions.InternalError(
-                    _(u"Several directories found, this should not happen")
+                    _("Several directories found, this should not happen")
                 )
             else:
                 directory = directories[0]
                 self.checkFilePermission(directory, peer_jid, perms_to_check)
-                parent = directory[u"id"]
+                parent = directory["id"]
         defer.returnValue((parent, []))
 
     @defer.inlineCallbacks
@@ -1357,8 +1357,8 @@
         """
         if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid:
             raise exceptions.InternalError(
-                u"if you want to disable permission check, both peer_jid and "
-                u"perms_to_check must be None"
+                "if you want to disable permission check, both peer_jid and "
+                "perms_to_check must be None"
             )
         if owner is not None:
             owner = owner.userhostJID()
@@ -1378,7 +1378,7 @@
             try:
                 parent_data = parent_data[0]
             except IndexError:
-                raise exceptions.DataError(u"mising parent")
+                raise exceptions.DataError("mising parent")
             yield self.checkPermissionToRoot(
                 client, parent_data, peer_jid, perms_to_check
             )
@@ -1414,7 +1414,7 @@
 
     @defer.inlineCallbacks
     def setFile(
-            self, client, name, file_id=None, version=u"", parent=None, path=None,
+            self, client, name, file_id=None, version="", parent=None, path=None,
             type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None,
             namespace=None, mime_type=None, created=None, modified=None, owner=None,
             access=None, extra=None, peer_jid=None, perms_to_check=(C.ACCESS_PERM_WRITE,)
@@ -1481,7 +1481,7 @@
         if type_ == C.FILE_TYPE_DIRECTORY:
             if any(version, file_hash, size, mime_type):
                 raise ValueError(
-                    u"version, file_hash, size and mime_type can't be set for a directory"
+                    "version, file_hash, size and mime_type can't be set for a directory"
                 )
         if owner is not None:
             owner = owner.userhostJID()
@@ -1498,7 +1498,7 @@
                     client,
                     name=new_dir,
                     file_id=new_dir_id,
-                    version=u"",
+                    version="",
                     parent=parent,
                     type_=C.FILE_TYPE_DIRECTORY,
                     namespace=namespace,
@@ -1509,7 +1509,7 @@
                 )
                 parent = new_dir_id
         elif parent is None:
-            parent = u""
+            parent = ""
 
         yield self.storage.setFile(
             client,
@@ -1552,35 +1552,35 @@
         @param files_path(unicode): path of the directory containing the actual files
         @param file_data(dict): data of the file to delete
         """
-        if file_data[u'owner'] != peer_jid:
+        if file_data['owner'] != peer_jid:
             raise exceptions.PermissionError(
-                u"file {file_name} can't be deleted, {peer_jid} is not the owner"
-                .format(file_name=file_data[u'name'], peer_jid=peer_jid.full()))
-        if file_data[u'type'] == C.FILE_TYPE_DIRECTORY:
-            sub_files = yield self.getFiles(client, peer_jid, parent=file_data[u'id'])
+                "file {file_name} can't be deleted, {peer_jid} is not the owner"
+                .format(file_name=file_data['name'], peer_jid=peer_jid.full()))
+        if file_data['type'] == C.FILE_TYPE_DIRECTORY:
+            sub_files = yield self.getFiles(client, peer_jid, parent=file_data['id'])
             if sub_files and not recursive:
-                raise exceptions.DataError(_(u"Can't delete directory, it is not empty"))
+                raise exceptions.DataError(_("Can't delete directory, it is not empty"))
             # we first delete the sub-files
             for sub_file_data in sub_files:
                 yield self._deleteFile(client, peer_jid, recursive, sub_file_data)
             # then the directory itself
-            yield self.storage.fileDelete(file_data[u'id'])
-        elif file_data[u'type'] == C.FILE_TYPE_FILE:
-            log.info(_(u"deleting file {name} with hash {file_hash}").format(
-                name=file_data[u'name'], file_hash=file_data[u'file_hash']))
-            yield self.storage.fileDelete(file_data[u'id'])
+            yield self.storage.fileDelete(file_data['id'])
+        elif file_data['type'] == C.FILE_TYPE_FILE:
+            log.info(_("deleting file {name} with hash {file_hash}").format(
+                name=file_data['name'], file_hash=file_data['file_hash']))
+            yield self.storage.fileDelete(file_data['id'])
             references = yield self.getFiles(
-                client, peer_jid, file_hash=file_data[u'file_hash'])
+                client, peer_jid, file_hash=file_data['file_hash'])
             if references:
-                log.debug(u"there are still references to the file, we keep it")
+                log.debug("there are still references to the file, we keep it")
             else:
-                file_path = os.path.join(files_path, file_data[u'file_hash'])
-                log.info(_(u"no reference left to {file_path}, deleting").format(
+                file_path = os.path.join(files_path, file_data['file_hash'])
+                log.info(_("no reference left to {file_path}, deleting").format(
                     file_path=file_path))
                 os.unlink(file_path)
         else:
-            raise exceptions.InternalError(u'Unexpected file type: {file_type}'
-                .format(file_type=file_data[u'type']))
+            raise exceptions.InternalError('Unexpected file type: {file_type}'
+                .format(file_type=file_data['type']))
 
     @defer.inlineCallbacks
     def fileDelete(self, client, peer_jid, file_id, recursive=False):
@@ -1595,11 +1595,11 @@
         #        should be checked too
         files_data = yield self.getFiles(client, peer_jid, file_id)
         if not files_data:
-            raise exceptions.NotFound(u"Can't find the file with id {file_id}".format(
+            raise exceptions.NotFound("Can't find the file with id {file_id}".format(
                 file_id=file_id))
         file_data = files_data[0]
-        if file_data[u"type"] != C.FILE_TYPE_DIRECTORY and recursive:
-            raise ValueError(u"recursive can only be set for directories")
+        if file_data["type"] != C.FILE_TYPE_DIRECTORY and recursive:
+            raise ValueError("recursive can only be set for directories")
         files_path = self.host.getLocalPath(None, C.FILES_DIR, profile=False)
         yield self._deleteFile(client, peer_jid, recursive, files_path, file_data)
 
@@ -1618,6 +1618,6 @@
         try:
             presence_data = self.getEntityDatum(entity_jid, "presence", client.profile)
         except KeyError:
-            log.debug(u"No presence information for {}".format(entity_jid))
+            log.debug("No presence information for {}".format(entity_jid))
             return False
         return presence_data.show != C.PRESENCE_UNAVAILABLE