diff libervia/backend/core/constants.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/core/constants.py@1c4f4aa36d98
children 10b6ad569157
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/backend/core/constants.py	Fri Jun 02 11:49:51 2023 +0200
@@ -0,0 +1,534 @@
+#!/usr/bin/env python3
+
+# Libervia: an XMPP client
+# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+try:
+    from xdg import BaseDirectory
+    from os.path import expanduser, realpath
+except ImportError:
+    BaseDirectory = None
+from os.path import dirname
+from typing import Final
+from libervia import backend
+
+
+class Const(object):
+
+    ## Application ##
+    APP_NAME = "Libervia"
+    APP_COMPONENT = "backend"
+    APP_NAME_ALT = "Libervia"
+    APP_NAME_FILE = "libervia"
+    APP_NAME_FULL = f"{APP_NAME} ({APP_COMPONENT})"
+    APP_VERSION = (
+        backend.__version__
+    )  # Please add 'D' at the end of version in sat/VERSION for dev versions
+    APP_RELEASE_NAME = "La Ruche"
+    APP_URL = "https://libervia.org"
+
+    ## Runtime ##
+    PLUGIN_EXT = "py"
+    HISTORY_SKIP = "skip"
+
+    ## Main config ##
+    DEFAULT_BRIDGE = "dbus"
+
+    ## Protocol ##
+    XMPP_C2S_PORT = 5222
+    XMPP_MAX_RETRIES = None
+    # default port used on Prosody, may differ on other servers
+    XMPP_COMPONENT_PORT = 5347
+
+    ## Parameters ##
+    NO_SECURITY_LIMIT = -1  #  FIXME: to rename
+    SECURITY_LIMIT_MAX = 0
+    INDIVIDUAL = "individual"
+    GENERAL = "general"
+    # General parameters
+    HISTORY_LIMIT = "History"
+    SHOW_OFFLINE_CONTACTS = "Offline contacts"
+    SHOW_EMPTY_GROUPS = "Empty groups"
+    # Parameters related to connection
+    FORCE_SERVER_PARAM = "Force server"
+    FORCE_PORT_PARAM = "Force port"
+    # Parameters related to encryption
+    PROFILE_PASS_PATH = ("General", "Password")
+    MEMORY_CRYPTO_NAMESPACE = "crypto"  # for the private persistent binary dict
+    MEMORY_CRYPTO_KEY = "personal_key"
+    # Parameters for static blog pages
+    # FIXME: blog constants should not be in core constants
+    STATIC_BLOG_KEY = "Blog page"
+    STATIC_BLOG_PARAM_TITLE = "Title"
+    STATIC_BLOG_PARAM_BANNER = "Banner"
+    STATIC_BLOG_PARAM_KEYWORDS = "Keywords"
+    STATIC_BLOG_PARAM_DESCRIPTION = "Description"
+
+    ## Menus ##
+    MENU_GLOBAL = "GLOBAL"
+    MENU_ROOM = "ROOM"
+    MENU_SINGLE = "SINGLE"
+    MENU_JID_CONTEXT = "JID_CONTEXT"
+    MENU_ROSTER_JID_CONTEXT = "ROSTER_JID_CONTEXT"
+    MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT"
+    MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT"
+
+    ## Profile and entities ##
+    PROF_KEY_NONE = "@NONE@"
+    PROF_KEY_DEFAULT = "@DEFAULT@"
+    PROF_KEY_ALL = "@ALL@"
+    ENTITY_ALL = "@ALL@"
+    ENTITY_ALL_RESOURCES = "@ALL_RESOURCES@"
+    ENTITY_MAIN_RESOURCE = "@MAIN_RESOURCE@"
+    ENTITY_CAP_HASH = "CAP_HASH"
+    ENTITY_TYPE = "type"
+    ENTITY_TYPE_MUC = "MUC"
+
+    ## Roster jids selection ##
+    PUBLIC = "PUBLIC"
+    ALL = (
+        "ALL"
+    )  # ALL means all known contacts, while PUBLIC means everybody, known or not
+    GROUP = "GROUP"
+    JID = "JID"
+
+    ## Messages ##
+    MESS_TYPE_INFO = "info"
+    MESS_TYPE_CHAT = "chat"
+    MESS_TYPE_ERROR = "error"
+    MESS_TYPE_GROUPCHAT = "groupchat"
+    MESS_TYPE_HEADLINE = "headline"
+    MESS_TYPE_NORMAL = "normal"
+    MESS_TYPE_AUTO = "auto"  # magic value to let the backend guess the type
+    MESS_TYPE_STANDARD = (
+        MESS_TYPE_CHAT,
+        MESS_TYPE_ERROR,
+        MESS_TYPE_GROUPCHAT,
+        MESS_TYPE_HEADLINE,
+        MESS_TYPE_NORMAL,
+    )
+    MESS_TYPE_ALL = MESS_TYPE_STANDARD + (MESS_TYPE_INFO, MESS_TYPE_AUTO)
+
+    MESS_EXTRA_INFO = "info_type"
+    EXTRA_INFO_DECR_ERR = "DECRYPTION_ERROR"
+    EXTRA_INFO_ENCR_ERR = "ENCRYPTION_ERROR"
+
+    # encryption is a key for plugins
+    MESS_KEY_ENCRYPTION: Final = "ENCRYPTION"
+    # encrypted is a key for frontends
+    MESS_KEY_ENCRYPTED = "encrypted"
+    MESS_KEY_TRUSTED = "trusted"
+
+    # File encryption algorithms
+    ENC_AES_GCM = "AES-GCM"
+
+    ## Chat ##
+    CHAT_ONE2ONE = "one2one"
+    CHAT_GROUP = "group"
+
+    ## Presence ##
+    PRESENCE_UNAVAILABLE = "unavailable"
+    PRESENCE_SHOW_AWAY = "away"
+    PRESENCE_SHOW_CHAT = "chat"
+    PRESENCE_SHOW_DND = "dnd"
+    PRESENCE_SHOW_XA = "xa"
+    PRESENCE_SHOW = "show"
+    PRESENCE_STATUSES = "statuses"
+    PRESENCE_STATUSES_DEFAULT = "default"
+    PRESENCE_PRIORITY = "priority"
+
+    ## Common namespaces ##
+    NS_XML = "http://www.w3.org/XML/1998/namespace"
+    NS_CLIENT = "jabber:client"
+    NS_COMPONENT = "jabber:component:accept"
+    NS_STREAM = (NS_CLIENT, NS_COMPONENT)
+    NS_FORWARD = "urn:xmpp:forward:0"
+    NS_DELAY = "urn:xmpp:delay"
+    NS_XHTML = "http://www.w3.org/1999/xhtml"
+
+    ## Common XPath ##
+
+    IQ_GET = '/iq[@type="get"]'
+    IQ_SET = '/iq[@type="set"]'
+
+    ## Directories ##
+
+    # directory for components specific data
+    COMPONENTS_DIR = "components"
+    CACHE_DIR = "cache"
+    # files in file dir are stored for long term
+    # files dir is global, i.e. for all profiles
+    FILES_DIR = "files"
+    # FILES_LINKS_DIR is a directory where files owned by a specific profile
+    # are linked to the global files directory. This way the directory can be
+    #  shared per profiles while keeping global directory where identical files
+    # shared between different profiles are not duplicated.
+    FILES_LINKS_DIR = "files_links"
+    # FILES_TMP_DIR is where profile's partially transfered files are put.
+    # Once transfer is completed, they are moved to FILES_DIR
+    FILES_TMP_DIR = "files_tmp"
+
+    ## Templates ##
+    TEMPLATE_TPL_DIR = "templates"
+    TEMPLATE_THEME_DEFAULT = "default"
+    TEMPLATE_STATIC_DIR = "static"
+    # templates i18n
+    KEY_LANG = "lang"
+    KEY_THEME = "theme"
+
+    ## Plugins ##
+
+    # PLUGIN_INFO keys
+    # XXX: we use PI instead of PLUG_INFO which would normally be used
+    #      to make the header more readable
+    PI_NAME = "name"
+    PI_IMPORT_NAME = "import_name"
+    PI_MAIN = "main"
+    PI_HANDLER = "handler"
+    PI_TYPE = (
+        "type"
+    )  #  FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import")
+    PI_MODES = "modes"
+    PI_PROTOCOLS = "protocols"
+    PI_DEPENDENCIES = "dependencies"
+    PI_RECOMMENDATIONS = "recommendations"
+    PI_DESCRIPTION = "description"
+    PI_USAGE = "usage"
+
+    # Types
+    PLUG_TYPE_XEP = "XEP"
+    PLUG_TYPE_MISC = "MISC"
+    PLUG_TYPE_EXP = "EXP"
+    PLUG_TYPE_SEC = "SEC"
+    PLUG_TYPE_SYNTAXE = "SYNTAXE"
+    PLUG_TYPE_PUBSUB = "PUBSUB"
+    PLUG_TYPE_BLOG = "BLOG"
+    PLUG_TYPE_IMPORT = "IMPORT"
+    PLUG_TYPE_ENTRY_POINT = "ENTRY_POINT"
+
+    # Modes
+    PLUG_MODE_CLIENT = "client"
+    PLUG_MODE_COMPONENT = "component"
+    PLUG_MODE_DEFAULT = (PLUG_MODE_CLIENT,)
+    PLUG_MODE_BOTH = (PLUG_MODE_CLIENT, PLUG_MODE_COMPONENT)
+
+    # names of widely used plugins
+    TEXT_CMDS = "TEXT-COMMANDS"
+
+    # PubSub event categories
+    PS_PEP = "PEP"
+    PS_MICROBLOG = "MICROBLOG"
+
+    # PubSub
+    PS_PUBLISH = "publish"
+    PS_RETRACT = "retract"  # used for items
+    PS_DELETE = "delete"  # used for nodes
+    PS_PURGE = "purge"  # used for nodes
+    PS_ITEM = "item"
+    PS_ITEMS = "items"  # Can contain publish and retract items
+    PS_EVENTS = (PS_ITEMS, PS_DELETE, PS_PURGE)
+
+    ## MESSAGE/NOTIFICATION LEVELS ##
+
+    LVL_INFO = "info"
+    LVL_WARNING = "warning"
+    LVL_ERROR = "error"
+
+    ## XMLUI ##
+    XMLUI_WINDOW = "window"
+    XMLUI_POPUP = "popup"
+    XMLUI_FORM = "form"
+    XMLUI_PARAM = "param"
+    XMLUI_DIALOG = "dialog"
+    XMLUI_DIALOG_CONFIRM = "confirm"
+    XMLUI_DIALOG_MESSAGE = "message"
+    XMLUI_DIALOG_NOTE = "note"
+    XMLUI_DIALOG_FILE = "file"
+    XMLUI_DATA_ANSWER = "answer"
+    XMLUI_DATA_CANCELLED = "cancelled"
+    XMLUI_DATA_TYPE = "type"
+    XMLUI_DATA_MESS = "message"
+    XMLUI_DATA_LVL = "level"
+    XMLUI_DATA_LVL_INFO = LVL_INFO
+    XMLUI_DATA_LVL_WARNING = LVL_WARNING
+    XMLUI_DATA_LVL_ERROR = LVL_ERROR
+    XMLUI_DATA_LVL_DEFAULT = XMLUI_DATA_LVL_INFO
+    XMLUI_DATA_LVLS = (XMLUI_DATA_LVL_INFO, XMLUI_DATA_LVL_WARNING, XMLUI_DATA_LVL_ERROR)
+    XMLUI_DATA_BTNS_SET = "buttons_set"
+    XMLUI_DATA_BTNS_SET_OKCANCEL = "ok/cancel"
+    XMLUI_DATA_BTNS_SET_YESNO = "yes/no"
+    XMLUI_DATA_BTNS_SET_DEFAULT = XMLUI_DATA_BTNS_SET_OKCANCEL
+    XMLUI_DATA_FILETYPE = "filetype"
+    XMLUI_DATA_FILETYPE_FILE = "file"
+    XMLUI_DATA_FILETYPE_DIR = "dir"
+    XMLUI_DATA_FILETYPE_DEFAULT = XMLUI_DATA_FILETYPE_FILE
+
+    ## Logging ##
+    LOG_LVL_DEBUG = "DEBUG"
+    LOG_LVL_INFO = "INFO"
+    LOG_LVL_WARNING = "WARNING"
+    LOG_LVL_ERROR = "ERROR"
+    LOG_LVL_CRITICAL = "CRITICAL"
+    LOG_LEVELS = (
+        LOG_LVL_DEBUG,
+        LOG_LVL_INFO,
+        LOG_LVL_WARNING,
+        LOG_LVL_ERROR,
+        LOG_LVL_CRITICAL,
+    )
+    LOG_BACKEND_STANDARD = "standard"
+    LOG_BACKEND_TWISTED = "twisted"
+    LOG_BACKEND_BASIC = "basic"
+    LOG_BACKEND_CUSTOM = "custom"
+    LOG_BASE_LOGGER = "root"
+    LOG_TWISTED_LOGGER = "twisted"
+    LOG_OPT_SECTION = "DEFAULT"  # section of sat.conf where log options should be
+    LOG_OPT_PREFIX = "log_"
+    # (option_name, default_value) tuples
+    LOG_OPT_COLORS = (
+        "colors",
+        "true",
+    )  # true for auto colors, force to have colors even if stdout is not a tty, false for no color
+    LOG_OPT_TAINTS_DICT = (
+        "levels_taints_dict",
+        {
+            LOG_LVL_DEBUG: ("cyan",),
+            LOG_LVL_INFO: (),
+            LOG_LVL_WARNING: ("yellow",),
+            LOG_LVL_ERROR: ("red", "blink", r"/!\ ", "blink_off"),
+            LOG_LVL_CRITICAL: ("bold", "red", "Guru Meditation ", "normal_weight"),
+        },
+    )
+    LOG_OPT_LEVEL = ("level", "info")
+    LOG_OPT_FORMAT = ("fmt", "%(message)s")  # similar to logging format.
+    LOG_OPT_LOGGER = ("logger", "")  # regex to filter logger name
+    LOG_OPT_OUTPUT_SEP = "//"
+    LOG_OPT_OUTPUT_DEFAULT = "default"
+    LOG_OPT_OUTPUT_MEMORY = "memory"
+    LOG_OPT_OUTPUT_MEMORY_LIMIT = 300
+    LOG_OPT_OUTPUT_FILE = "file"  # file is implicit if only output
+    LOG_OPT_OUTPUT = (
+        "output",
+        LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT,
+    )  # //default = normal output (stderr or a file with twistd), path/to/file for a file (must be the first if used), //memory for memory (options can be put in parenthesis, e.g.: //memory(500) for a 500 lines memory)
+
+    ## action constants ##
+    META_TYPE_FILE = "file"
+    META_TYPE_CALL = "call"
+    META_TYPE_OVERWRITE = "overwrite"
+    META_TYPE_NOT_IN_ROSTER_LEAK = "not_in_roster_leak"
+    META_SUBTYPE_CALL_AUDIO = "audio"
+    META_SUBTYPE_CALL_VIDEO = "video"
+
+    ## HARD-CODED ACTIONS IDS (generated with uuid.uuid4) ##
+    AUTHENTICATE_PROFILE_ID = "b03bbfa8-a4ae-4734-a248-06ce6c7cf562"
+    CHANGE_XMPP_PASSWD_ID = "878b9387-de2b-413b-950f-e424a147bcd0"
+
+    ## Text values ##
+    BOOL_TRUE = "true"
+    BOOL_FALSE = "false"
+
+    ## Special values used in bridge methods calls ##
+    HISTORY_LIMIT_DEFAULT = -1
+    HISTORY_LIMIT_NONE = -2
+
+    ## Progress error special values ##
+    PROGRESS_ERROR_DECLINED = "declined"  #  session has been declined by peer user
+    PROGRESS_ERROR_FAILED = "failed"  #  something went wrong with the session
+
+    ## Files ##
+    FILE_TYPE_DIRECTORY = "directory"
+    FILE_TYPE_FILE = "file"
+    # when filename can't be found automatically, this one will be used
+    FILE_DEFAULT_NAME = "unnamed"
+
+    ## Permissions management ##
+    ACCESS_PERM_READ = "read"
+    ACCESS_PERM_WRITE = "write"
+    ACCESS_PERMS = {ACCESS_PERM_READ, ACCESS_PERM_WRITE}
+    ACCESS_TYPE_PUBLIC = "public"
+    ACCESS_TYPE_WHITELIST = "whitelist"
+    ACCESS_TYPES = (ACCESS_TYPE_PUBLIC, ACCESS_TYPE_WHITELIST)
+
+    ## Common data keys ##
+    KEY_THUMBNAILS = "thumbnails"
+    KEY_PROGRESS_ID = "progress_id"
+    KEY_ATTACHMENTS = "attachments"
+    KEY_ATTACHMENTS_MEDIA_TYPE = "media_type"
+    KEY_ATTACHMENTS_PREVIEW = "preview"
+    KEY_ATTACHMENTS_RESIZE = "resize"
+
+
+    ## Common extra keys/values ##
+    KEY_ORDER_BY = "order_by"
+    KEY_USE_CACHE = "use_cache"
+    KEY_DECRYPT = "decrypt"
+
+    ORDER_BY_CREATION = 'creation'
+    ORDER_BY_MODIFICATION = 'modification'
+
+    # internationalisation
+    DEFAULT_LOCALE = "en_GB"
+
+    ## Command Line ##
+
+    # Exit codes used by CLI applications
+    EXIT_OK = 0
+    EXIT_ERROR = 1  # generic error, when nothing else match
+    EXIT_BAD_ARG = 2  # arguments given by user are bad
+    EXIT_BRIDGE_ERROR = 3  # can't connect to bridge
+    EXIT_BRIDGE_ERRBACK = 4  # something went wrong when calling a bridge method
+    EXIT_BACKEND_NOT_FOUND = 5  # can't find backend with this bride
+    EXIT_NOT_FOUND = 16  # an item required by a command was not found
+    EXIT_DATA_ERROR = 17  # data needed for a command is invalid
+    EXIT_MISSING_FEATURE = 18  # a needed plugin or feature is not available
+    EXIT_CONFLICT = 19  # an item already exists
+    EXIT_USER_CANCELLED = 20  # user cancelled action
+    EXIT_INTERNAL_ERROR = 111  # unexpected error
+    EXIT_FILE_NOT_EXE = (
+        126
+    )  # a file to be executed was found, but it was not an executable utility (cf. man 1 exit)
+    EXIT_CMD_NOT_FOUND = 127  # a utility to be executed was not found (cf. man 1 exit)
+    EXIT_CMD_ERROR = 127  # a utility to be executed returned an error exit code
+    EXIT_SIGNAL_INT = 128  # a command was interrupted by a signal (cf. man 1 exit)
+
+    ## Misc ##
+    SAVEFILE_DATABASE = APP_NAME_FILE + ".db"
+    IQ_SET = '/iq[@type="set"]'
+    ENV_PREFIX = "SAT_"  # Prefix used for environment variables
+    IGNORE = "ignore"
+    NO_LIMIT = -1  # used in bridge when a integer value is expected
+    DEFAULT_MAX_AGE = 1209600  # default max age of cached files, in seconds
+    STANZA_NAMES = ("iq", "message", "presence")
+
+    # Stream Hooks
+    STREAM_HOOK_SEND = "send"
+    STREAM_HOOK_RECEIVE = "receive"
+
+    @classmethod
+    def LOG_OPTIONS(cls):
+        """Return options checked for logs"""
+        # XXX: we use a classmethod so we can use Const inheritance to change default options
+        return (
+            cls.LOG_OPT_COLORS,
+            cls.LOG_OPT_TAINTS_DICT,
+            cls.LOG_OPT_LEVEL,
+            cls.LOG_OPT_FORMAT,
+            cls.LOG_OPT_LOGGER,
+            cls.LOG_OPT_OUTPUT,
+        )
+
+    @classmethod
+    def bool(cls, value: str) -> bool:
+        """@return (bool): bool value for associated constant"""
+        assert isinstance(value, str)
+        return value.lower() in (cls.BOOL_TRUE, "1", "yes", "on")
+
+    @classmethod
+    def bool_const(cls, value: bool) -> str:
+        """@return (str): constant associated to bool value"""
+        assert isinstance(value, bool)
+        return cls.BOOL_TRUE if value else cls.BOOL_FALSE
+
+
+
+## Configuration ##
+if (
+    BaseDirectory
+):  # skipped when xdg module is not available (should not happen in backend)
+    if "org.libervia.cagou" in BaseDirectory.__file__:
+        # FIXME: hack to make config read from the right location on Android
+        # TODO: fix it in a more proper way
+
+        # we need to use Android API to get downloads directory
+        import os.path
+        from jnius import autoclass
+
+        # we don't want the very verbose jnius log when we are in DEBUG level
+        import logging
+        logging.getLogger('jnius').setLevel(logging.WARNING)
+        logging.getLogger('jnius.reflect').setLevel(logging.WARNING)
+
+        Environment = autoclass("android.os.Environment")
+
+        BaseDirectory = None
+        Const.DEFAULT_CONFIG = {
+            "local_dir": "/data/data/org.libervia.cagou/app",
+            "media_dir": "/data/data/org.libervia.cagou/files/app/media",
+            # FIXME: temporary location for downloads, need to call API properly
+            "downloads_dir": os.path.join(
+                Environment.getExternalStoragePublicDirectory(
+                    Environment.DIRECTORY_DOWNLOADS
+                ).getAbsolutePath(),
+                Const.APP_NAME_FILE,
+            ),
+            "pid_dir": "%(local_dir)s",
+            "log_dir": "%(local_dir)s",
+        }
+        Const.CONFIG_FILES = [
+            "/data/data/org.libervia.cagou/files/app/android/"
+            + Const.APP_NAME_FILE
+            + ".conf"
+        ]
+    else:
+        import os
+        # we use parent of "sat" module dir as last config path, this is useful for
+        # per instance configurations (e.g. a dev instance and a main instance)
+        root_dir = dirname(dirname(backend.__file__)) + '/'
+        Const.CONFIG_PATHS = (
+            # /etc/_sat.conf is used for system-related settings (e.g. when media_dir
+            # is set by the distribution and has not reason to change, or in a Docker
+            # image)
+            ["/etc/_", "/etc/", "~/", "~/."]
+            + [
+                "{}/".format(path)
+                for path in list(BaseDirectory.load_config_paths(Const.APP_NAME_FILE))
+            ]
+            # this is to handle legacy sat.conf
+            + [
+                "{}/".format(path)
+                for path in list(BaseDirectory.load_config_paths("sat"))
+            ]
+            + [root_dir]
+        )
+
+        # on recent versions of Flatpak, FLATPAK_ID is set at run time
+        # it seems that this is not the case on older versions,
+        # but FLATPAK_SANDBOX_DIR seems set then
+        if os.getenv('FLATPAK_ID') or os.getenv('FLATPAK_SANDBOX_DIR'):
+            # for Flatpak, the conf can't be set in /etc or $HOME, so we have
+            # to add /app
+            Const.CONFIG_PATHS.append('/app/')
+
+        ## Configuration ##
+        Const.DEFAULT_CONFIG = {
+            "media_dir": "/usr/share/" + Const.APP_NAME_FILE + "/media",
+            "local_dir": BaseDirectory.save_data_path(Const.APP_NAME_FILE),
+            "downloads_dir": "~/Downloads/" + Const.APP_NAME_FILE,
+            "pid_dir": "%(local_dir)s",
+            "log_dir": "%(local_dir)s",
+        }
+
+        # List of the configuration filenames sorted by ascending priority
+        Const.CONFIG_FILES = [
+            realpath(expanduser(path) + Const.APP_NAME_FILE + ".conf")
+            for path in Const.CONFIG_PATHS
+        ] + [
+            # legacy sat.conf
+            realpath(expanduser(path) + "sat.conf")
+            for path in Const.CONFIG_PATHS
+        ]
+