comparison 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
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # Libervia: an XMPP client
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
5
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
15
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 try:
20 from xdg import BaseDirectory
21 from os.path import expanduser, realpath
22 except ImportError:
23 BaseDirectory = None
24 from os.path import dirname
25 from typing import Final
26 from libervia import backend
27
28
29 class Const(object):
30
31 ## Application ##
32 APP_NAME = "Libervia"
33 APP_COMPONENT = "backend"
34 APP_NAME_ALT = "Libervia"
35 APP_NAME_FILE = "libervia"
36 APP_NAME_FULL = f"{APP_NAME} ({APP_COMPONENT})"
37 APP_VERSION = (
38 backend.__version__
39 ) # Please add 'D' at the end of version in sat/VERSION for dev versions
40 APP_RELEASE_NAME = "La Ruche"
41 APP_URL = "https://libervia.org"
42
43 ## Runtime ##
44 PLUGIN_EXT = "py"
45 HISTORY_SKIP = "skip"
46
47 ## Main config ##
48 DEFAULT_BRIDGE = "dbus"
49
50 ## Protocol ##
51 XMPP_C2S_PORT = 5222
52 XMPP_MAX_RETRIES = None
53 # default port used on Prosody, may differ on other servers
54 XMPP_COMPONENT_PORT = 5347
55
56 ## Parameters ##
57 NO_SECURITY_LIMIT = -1 #  FIXME: to rename
58 SECURITY_LIMIT_MAX = 0
59 INDIVIDUAL = "individual"
60 GENERAL = "general"
61 # General parameters
62 HISTORY_LIMIT = "History"
63 SHOW_OFFLINE_CONTACTS = "Offline contacts"
64 SHOW_EMPTY_GROUPS = "Empty groups"
65 # Parameters related to connection
66 FORCE_SERVER_PARAM = "Force server"
67 FORCE_PORT_PARAM = "Force port"
68 # Parameters related to encryption
69 PROFILE_PASS_PATH = ("General", "Password")
70 MEMORY_CRYPTO_NAMESPACE = "crypto" # for the private persistent binary dict
71 MEMORY_CRYPTO_KEY = "personal_key"
72 # Parameters for static blog pages
73 # FIXME: blog constants should not be in core constants
74 STATIC_BLOG_KEY = "Blog page"
75 STATIC_BLOG_PARAM_TITLE = "Title"
76 STATIC_BLOG_PARAM_BANNER = "Banner"
77 STATIC_BLOG_PARAM_KEYWORDS = "Keywords"
78 STATIC_BLOG_PARAM_DESCRIPTION = "Description"
79
80 ## Menus ##
81 MENU_GLOBAL = "GLOBAL"
82 MENU_ROOM = "ROOM"
83 MENU_SINGLE = "SINGLE"
84 MENU_JID_CONTEXT = "JID_CONTEXT"
85 MENU_ROSTER_JID_CONTEXT = "ROSTER_JID_CONTEXT"
86 MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT"
87 MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT"
88
89 ## Profile and entities ##
90 PROF_KEY_NONE = "@NONE@"
91 PROF_KEY_DEFAULT = "@DEFAULT@"
92 PROF_KEY_ALL = "@ALL@"
93 ENTITY_ALL = "@ALL@"
94 ENTITY_ALL_RESOURCES = "@ALL_RESOURCES@"
95 ENTITY_MAIN_RESOURCE = "@MAIN_RESOURCE@"
96 ENTITY_CAP_HASH = "CAP_HASH"
97 ENTITY_TYPE = "type"
98 ENTITY_TYPE_MUC = "MUC"
99
100 ## Roster jids selection ##
101 PUBLIC = "PUBLIC"
102 ALL = (
103 "ALL"
104 ) # ALL means all known contacts, while PUBLIC means everybody, known or not
105 GROUP = "GROUP"
106 JID = "JID"
107
108 ## Messages ##
109 MESS_TYPE_INFO = "info"
110 MESS_TYPE_CHAT = "chat"
111 MESS_TYPE_ERROR = "error"
112 MESS_TYPE_GROUPCHAT = "groupchat"
113 MESS_TYPE_HEADLINE = "headline"
114 MESS_TYPE_NORMAL = "normal"
115 MESS_TYPE_AUTO = "auto" # magic value to let the backend guess the type
116 MESS_TYPE_STANDARD = (
117 MESS_TYPE_CHAT,
118 MESS_TYPE_ERROR,
119 MESS_TYPE_GROUPCHAT,
120 MESS_TYPE_HEADLINE,
121 MESS_TYPE_NORMAL,
122 )
123 MESS_TYPE_ALL = MESS_TYPE_STANDARD + (MESS_TYPE_INFO, MESS_TYPE_AUTO)
124
125 MESS_EXTRA_INFO = "info_type"
126 EXTRA_INFO_DECR_ERR = "DECRYPTION_ERROR"
127 EXTRA_INFO_ENCR_ERR = "ENCRYPTION_ERROR"
128
129 # encryption is a key for plugins
130 MESS_KEY_ENCRYPTION: Final = "ENCRYPTION"
131 # encrypted is a key for frontends
132 MESS_KEY_ENCRYPTED = "encrypted"
133 MESS_KEY_TRUSTED = "trusted"
134
135 # File encryption algorithms
136 ENC_AES_GCM = "AES-GCM"
137
138 ## Chat ##
139 CHAT_ONE2ONE = "one2one"
140 CHAT_GROUP = "group"
141
142 ## Presence ##
143 PRESENCE_UNAVAILABLE = "unavailable"
144 PRESENCE_SHOW_AWAY = "away"
145 PRESENCE_SHOW_CHAT = "chat"
146 PRESENCE_SHOW_DND = "dnd"
147 PRESENCE_SHOW_XA = "xa"
148 PRESENCE_SHOW = "show"
149 PRESENCE_STATUSES = "statuses"
150 PRESENCE_STATUSES_DEFAULT = "default"
151 PRESENCE_PRIORITY = "priority"
152
153 ## Common namespaces ##
154 NS_XML = "http://www.w3.org/XML/1998/namespace"
155 NS_CLIENT = "jabber:client"
156 NS_COMPONENT = "jabber:component:accept"
157 NS_STREAM = (NS_CLIENT, NS_COMPONENT)
158 NS_FORWARD = "urn:xmpp:forward:0"
159 NS_DELAY = "urn:xmpp:delay"
160 NS_XHTML = "http://www.w3.org/1999/xhtml"
161
162 ## Common XPath ##
163
164 IQ_GET = '/iq[@type="get"]'
165 IQ_SET = '/iq[@type="set"]'
166
167 ## Directories ##
168
169 # directory for components specific data
170 COMPONENTS_DIR = "components"
171 CACHE_DIR = "cache"
172 # files in file dir are stored for long term
173 # files dir is global, i.e. for all profiles
174 FILES_DIR = "files"
175 # FILES_LINKS_DIR is a directory where files owned by a specific profile
176 # are linked to the global files directory. This way the directory can be
177 #  shared per profiles while keeping global directory where identical files
178 # shared between different profiles are not duplicated.
179 FILES_LINKS_DIR = "files_links"
180 # FILES_TMP_DIR is where profile's partially transfered files are put.
181 # Once transfer is completed, they are moved to FILES_DIR
182 FILES_TMP_DIR = "files_tmp"
183
184 ## Templates ##
185 TEMPLATE_TPL_DIR = "templates"
186 TEMPLATE_THEME_DEFAULT = "default"
187 TEMPLATE_STATIC_DIR = "static"
188 # templates i18n
189 KEY_LANG = "lang"
190 KEY_THEME = "theme"
191
192 ## Plugins ##
193
194 # PLUGIN_INFO keys
195 # XXX: we use PI instead of PLUG_INFO which would normally be used
196 # to make the header more readable
197 PI_NAME = "name"
198 PI_IMPORT_NAME = "import_name"
199 PI_MAIN = "main"
200 PI_HANDLER = "handler"
201 PI_TYPE = (
202 "type"
203 ) #  FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import")
204 PI_MODES = "modes"
205 PI_PROTOCOLS = "protocols"
206 PI_DEPENDENCIES = "dependencies"
207 PI_RECOMMENDATIONS = "recommendations"
208 PI_DESCRIPTION = "description"
209 PI_USAGE = "usage"
210
211 # Types
212 PLUG_TYPE_XEP = "XEP"
213 PLUG_TYPE_MISC = "MISC"
214 PLUG_TYPE_EXP = "EXP"
215 PLUG_TYPE_SEC = "SEC"
216 PLUG_TYPE_SYNTAXE = "SYNTAXE"
217 PLUG_TYPE_PUBSUB = "PUBSUB"
218 PLUG_TYPE_BLOG = "BLOG"
219 PLUG_TYPE_IMPORT = "IMPORT"
220 PLUG_TYPE_ENTRY_POINT = "ENTRY_POINT"
221
222 # Modes
223 PLUG_MODE_CLIENT = "client"
224 PLUG_MODE_COMPONENT = "component"
225 PLUG_MODE_DEFAULT = (PLUG_MODE_CLIENT,)
226 PLUG_MODE_BOTH = (PLUG_MODE_CLIENT, PLUG_MODE_COMPONENT)
227
228 # names of widely used plugins
229 TEXT_CMDS = "TEXT-COMMANDS"
230
231 # PubSub event categories
232 PS_PEP = "PEP"
233 PS_MICROBLOG = "MICROBLOG"
234
235 # PubSub
236 PS_PUBLISH = "publish"
237 PS_RETRACT = "retract" # used for items
238 PS_DELETE = "delete" # used for nodes
239 PS_PURGE = "purge" # used for nodes
240 PS_ITEM = "item"
241 PS_ITEMS = "items" # Can contain publish and retract items
242 PS_EVENTS = (PS_ITEMS, PS_DELETE, PS_PURGE)
243
244 ## MESSAGE/NOTIFICATION LEVELS ##
245
246 LVL_INFO = "info"
247 LVL_WARNING = "warning"
248 LVL_ERROR = "error"
249
250 ## XMLUI ##
251 XMLUI_WINDOW = "window"
252 XMLUI_POPUP = "popup"
253 XMLUI_FORM = "form"
254 XMLUI_PARAM = "param"
255 XMLUI_DIALOG = "dialog"
256 XMLUI_DIALOG_CONFIRM = "confirm"
257 XMLUI_DIALOG_MESSAGE = "message"
258 XMLUI_DIALOG_NOTE = "note"
259 XMLUI_DIALOG_FILE = "file"
260 XMLUI_DATA_ANSWER = "answer"
261 XMLUI_DATA_CANCELLED = "cancelled"
262 XMLUI_DATA_TYPE = "type"
263 XMLUI_DATA_MESS = "message"
264 XMLUI_DATA_LVL = "level"
265 XMLUI_DATA_LVL_INFO = LVL_INFO
266 XMLUI_DATA_LVL_WARNING = LVL_WARNING
267 XMLUI_DATA_LVL_ERROR = LVL_ERROR
268 XMLUI_DATA_LVL_DEFAULT = XMLUI_DATA_LVL_INFO
269 XMLUI_DATA_LVLS = (XMLUI_DATA_LVL_INFO, XMLUI_DATA_LVL_WARNING, XMLUI_DATA_LVL_ERROR)
270 XMLUI_DATA_BTNS_SET = "buttons_set"
271 XMLUI_DATA_BTNS_SET_OKCANCEL = "ok/cancel"
272 XMLUI_DATA_BTNS_SET_YESNO = "yes/no"
273 XMLUI_DATA_BTNS_SET_DEFAULT = XMLUI_DATA_BTNS_SET_OKCANCEL
274 XMLUI_DATA_FILETYPE = "filetype"
275 XMLUI_DATA_FILETYPE_FILE = "file"
276 XMLUI_DATA_FILETYPE_DIR = "dir"
277 XMLUI_DATA_FILETYPE_DEFAULT = XMLUI_DATA_FILETYPE_FILE
278
279 ## Logging ##
280 LOG_LVL_DEBUG = "DEBUG"
281 LOG_LVL_INFO = "INFO"
282 LOG_LVL_WARNING = "WARNING"
283 LOG_LVL_ERROR = "ERROR"
284 LOG_LVL_CRITICAL = "CRITICAL"
285 LOG_LEVELS = (
286 LOG_LVL_DEBUG,
287 LOG_LVL_INFO,
288 LOG_LVL_WARNING,
289 LOG_LVL_ERROR,
290 LOG_LVL_CRITICAL,
291 )
292 LOG_BACKEND_STANDARD = "standard"
293 LOG_BACKEND_TWISTED = "twisted"
294 LOG_BACKEND_BASIC = "basic"
295 LOG_BACKEND_CUSTOM = "custom"
296 LOG_BASE_LOGGER = "root"
297 LOG_TWISTED_LOGGER = "twisted"
298 LOG_OPT_SECTION = "DEFAULT" # section of sat.conf where log options should be
299 LOG_OPT_PREFIX = "log_"
300 # (option_name, default_value) tuples
301 LOG_OPT_COLORS = (
302 "colors",
303 "true",
304 ) # true for auto colors, force to have colors even if stdout is not a tty, false for no color
305 LOG_OPT_TAINTS_DICT = (
306 "levels_taints_dict",
307 {
308 LOG_LVL_DEBUG: ("cyan",),
309 LOG_LVL_INFO: (),
310 LOG_LVL_WARNING: ("yellow",),
311 LOG_LVL_ERROR: ("red", "blink", r"/!\ ", "blink_off"),
312 LOG_LVL_CRITICAL: ("bold", "red", "Guru Meditation ", "normal_weight"),
313 },
314 )
315 LOG_OPT_LEVEL = ("level", "info")
316 LOG_OPT_FORMAT = ("fmt", "%(message)s") # similar to logging format.
317 LOG_OPT_LOGGER = ("logger", "") # regex to filter logger name
318 LOG_OPT_OUTPUT_SEP = "//"
319 LOG_OPT_OUTPUT_DEFAULT = "default"
320 LOG_OPT_OUTPUT_MEMORY = "memory"
321 LOG_OPT_OUTPUT_MEMORY_LIMIT = 300
322 LOG_OPT_OUTPUT_FILE = "file" # file is implicit if only output
323 LOG_OPT_OUTPUT = (
324 "output",
325 LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT,
326 ) # //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)
327
328 ## action constants ##
329 META_TYPE_FILE = "file"
330 META_TYPE_CALL = "call"
331 META_TYPE_OVERWRITE = "overwrite"
332 META_TYPE_NOT_IN_ROSTER_LEAK = "not_in_roster_leak"
333 META_SUBTYPE_CALL_AUDIO = "audio"
334 META_SUBTYPE_CALL_VIDEO = "video"
335
336 ## HARD-CODED ACTIONS IDS (generated with uuid.uuid4) ##
337 AUTHENTICATE_PROFILE_ID = "b03bbfa8-a4ae-4734-a248-06ce6c7cf562"
338 CHANGE_XMPP_PASSWD_ID = "878b9387-de2b-413b-950f-e424a147bcd0"
339
340 ## Text values ##
341 BOOL_TRUE = "true"
342 BOOL_FALSE = "false"
343
344 ## Special values used in bridge methods calls ##
345 HISTORY_LIMIT_DEFAULT = -1
346 HISTORY_LIMIT_NONE = -2
347
348 ## Progress error special values ##
349 PROGRESS_ERROR_DECLINED = "declined" #  session has been declined by peer user
350 PROGRESS_ERROR_FAILED = "failed" #  something went wrong with the session
351
352 ## Files ##
353 FILE_TYPE_DIRECTORY = "directory"
354 FILE_TYPE_FILE = "file"
355 # when filename can't be found automatically, this one will be used
356 FILE_DEFAULT_NAME = "unnamed"
357
358 ## Permissions management ##
359 ACCESS_PERM_READ = "read"
360 ACCESS_PERM_WRITE = "write"
361 ACCESS_PERMS = {ACCESS_PERM_READ, ACCESS_PERM_WRITE}
362 ACCESS_TYPE_PUBLIC = "public"
363 ACCESS_TYPE_WHITELIST = "whitelist"
364 ACCESS_TYPES = (ACCESS_TYPE_PUBLIC, ACCESS_TYPE_WHITELIST)
365
366 ## Common data keys ##
367 KEY_THUMBNAILS = "thumbnails"
368 KEY_PROGRESS_ID = "progress_id"
369 KEY_ATTACHMENTS = "attachments"
370 KEY_ATTACHMENTS_MEDIA_TYPE = "media_type"
371 KEY_ATTACHMENTS_PREVIEW = "preview"
372 KEY_ATTACHMENTS_RESIZE = "resize"
373
374
375 ## Common extra keys/values ##
376 KEY_ORDER_BY = "order_by"
377 KEY_USE_CACHE = "use_cache"
378 KEY_DECRYPT = "decrypt"
379
380 ORDER_BY_CREATION = 'creation'
381 ORDER_BY_MODIFICATION = 'modification'
382
383 # internationalisation
384 DEFAULT_LOCALE = "en_GB"
385
386 ## Command Line ##
387
388 # Exit codes used by CLI applications
389 EXIT_OK = 0
390 EXIT_ERROR = 1 # generic error, when nothing else match
391 EXIT_BAD_ARG = 2 # arguments given by user are bad
392 EXIT_BRIDGE_ERROR = 3 # can't connect to bridge
393 EXIT_BRIDGE_ERRBACK = 4 # something went wrong when calling a bridge method
394 EXIT_BACKEND_NOT_FOUND = 5 # can't find backend with this bride
395 EXIT_NOT_FOUND = 16 # an item required by a command was not found
396 EXIT_DATA_ERROR = 17 # data needed for a command is invalid
397 EXIT_MISSING_FEATURE = 18 # a needed plugin or feature is not available
398 EXIT_CONFLICT = 19 # an item already exists
399 EXIT_USER_CANCELLED = 20 # user cancelled action
400 EXIT_INTERNAL_ERROR = 111 # unexpected error
401 EXIT_FILE_NOT_EXE = (
402 126
403 ) # a file to be executed was found, but it was not an executable utility (cf. man 1 exit)
404 EXIT_CMD_NOT_FOUND = 127 # a utility to be executed was not found (cf. man 1 exit)
405 EXIT_CMD_ERROR = 127 # a utility to be executed returned an error exit code
406 EXIT_SIGNAL_INT = 128 # a command was interrupted by a signal (cf. man 1 exit)
407
408 ## Misc ##
409 SAVEFILE_DATABASE = APP_NAME_FILE + ".db"
410 IQ_SET = '/iq[@type="set"]'
411 ENV_PREFIX = "SAT_" # Prefix used for environment variables
412 IGNORE = "ignore"
413 NO_LIMIT = -1 # used in bridge when a integer value is expected
414 DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds
415 STANZA_NAMES = ("iq", "message", "presence")
416
417 # Stream Hooks
418 STREAM_HOOK_SEND = "send"
419 STREAM_HOOK_RECEIVE = "receive"
420
421 @classmethod
422 def LOG_OPTIONS(cls):
423 """Return options checked for logs"""
424 # XXX: we use a classmethod so we can use Const inheritance to change default options
425 return (
426 cls.LOG_OPT_COLORS,
427 cls.LOG_OPT_TAINTS_DICT,
428 cls.LOG_OPT_LEVEL,
429 cls.LOG_OPT_FORMAT,
430 cls.LOG_OPT_LOGGER,
431 cls.LOG_OPT_OUTPUT,
432 )
433
434 @classmethod
435 def bool(cls, value: str) -> bool:
436 """@return (bool): bool value for associated constant"""
437 assert isinstance(value, str)
438 return value.lower() in (cls.BOOL_TRUE, "1", "yes", "on")
439
440 @classmethod
441 def bool_const(cls, value: bool) -> str:
442 """@return (str): constant associated to bool value"""
443 assert isinstance(value, bool)
444 return cls.BOOL_TRUE if value else cls.BOOL_FALSE
445
446
447
448 ## Configuration ##
449 if (
450 BaseDirectory
451 ): # skipped when xdg module is not available (should not happen in backend)
452 if "org.libervia.cagou" in BaseDirectory.__file__:
453 # FIXME: hack to make config read from the right location on Android
454 # TODO: fix it in a more proper way
455
456 # we need to use Android API to get downloads directory
457 import os.path
458 from jnius import autoclass
459
460 # we don't want the very verbose jnius log when we are in DEBUG level
461 import logging
462 logging.getLogger('jnius').setLevel(logging.WARNING)
463 logging.getLogger('jnius.reflect').setLevel(logging.WARNING)
464
465 Environment = autoclass("android.os.Environment")
466
467 BaseDirectory = None
468 Const.DEFAULT_CONFIG = {
469 "local_dir": "/data/data/org.libervia.cagou/app",
470 "media_dir": "/data/data/org.libervia.cagou/files/app/media",
471 # FIXME: temporary location for downloads, need to call API properly
472 "downloads_dir": os.path.join(
473 Environment.getExternalStoragePublicDirectory(
474 Environment.DIRECTORY_DOWNLOADS
475 ).getAbsolutePath(),
476 Const.APP_NAME_FILE,
477 ),
478 "pid_dir": "%(local_dir)s",
479 "log_dir": "%(local_dir)s",
480 }
481 Const.CONFIG_FILES = [
482 "/data/data/org.libervia.cagou/files/app/android/"
483 + Const.APP_NAME_FILE
484 + ".conf"
485 ]
486 else:
487 import os
488 # we use parent of "sat" module dir as last config path, this is useful for
489 # per instance configurations (e.g. a dev instance and a main instance)
490 root_dir = dirname(dirname(backend.__file__)) + '/'
491 Const.CONFIG_PATHS = (
492 # /etc/_sat.conf is used for system-related settings (e.g. when media_dir
493 # is set by the distribution and has not reason to change, or in a Docker
494 # image)
495 ["/etc/_", "/etc/", "~/", "~/."]
496 + [
497 "{}/".format(path)
498 for path in list(BaseDirectory.load_config_paths(Const.APP_NAME_FILE))
499 ]
500 # this is to handle legacy sat.conf
501 + [
502 "{}/".format(path)
503 for path in list(BaseDirectory.load_config_paths("sat"))
504 ]
505 + [root_dir]
506 )
507
508 # on recent versions of Flatpak, FLATPAK_ID is set at run time
509 # it seems that this is not the case on older versions,
510 # but FLATPAK_SANDBOX_DIR seems set then
511 if os.getenv('FLATPAK_ID') or os.getenv('FLATPAK_SANDBOX_DIR'):
512 # for Flatpak, the conf can't be set in /etc or $HOME, so we have
513 # to add /app
514 Const.CONFIG_PATHS.append('/app/')
515
516 ## Configuration ##
517 Const.DEFAULT_CONFIG = {
518 "media_dir": "/usr/share/" + Const.APP_NAME_FILE + "/media",
519 "local_dir": BaseDirectory.save_data_path(Const.APP_NAME_FILE),
520 "downloads_dir": "~/Downloads/" + Const.APP_NAME_FILE,
521 "pid_dir": "%(local_dir)s",
522 "log_dir": "%(local_dir)s",
523 }
524
525 # List of the configuration filenames sorted by ascending priority
526 Const.CONFIG_FILES = [
527 realpath(expanduser(path) + Const.APP_NAME_FILE + ".conf")
528 for path in Const.CONFIG_PATHS
529 ] + [
530 # legacy sat.conf
531 realpath(expanduser(path) + "sat.conf")
532 for path in Const.CONFIG_PATHS
533 ]
534