summaryrefslogtreecommitdiff
path: root/poezio/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'poezio/config.py')
-rw-r--r--poezio/config.py289
1 files changed, 155 insertions, 134 deletions
diff --git a/poezio/config.py b/poezio/config.py
index 8da71071..4eb43cad 100644
--- a/poezio/config.py
+++ b/poezio/config.py
@@ -10,35 +10,37 @@ TODO: get http://bugs.python.org/issue1410680 fixed, one day, in order
to remove our ugly custom I/O methods.
"""
+import logging
import logging.config
import os
-import stat
import sys
-import pkg_resources
from configparser import RawConfigParser, NoOptionError, NoSectionError
from pathlib import Path
-from shutil import copy2
-from typing import Callable, Dict, List, Optional, Union, Tuple
+from typing import Dict, List, Optional, Union, Tuple, cast, Any
-from poezio.args import parse_args
from poezio import xdg
+from slixmpp import JID, InvalidJID
+
+log = logging.getLogger(__name__) # type: logging.Logger
ConfigValue = Union[str, int, float, bool]
-DEFSECTION = "Poezio"
+ConfigDict = Dict[str, Dict[str, ConfigValue]]
+
+USE_DEFAULT_SECTION = '__DEFAULT SECTION PLACEHOLDER__'
-DEFAULT_CONFIG = {
+DEFAULT_CONFIG: ConfigDict = {
'Poezio': {
'ack_message_receipts': True,
'add_space_after_completion': True,
'after_completion': ',',
'alternative_nickname': '',
'auto_reconnect': True,
+ 'autocolor_tab_names': False,
'autorejoin_delay': '5',
'autorejoin': False,
'beep_on': 'highlight private invite disconnect',
- 'bookmark_on_join': False,
'ca_cert_path': '',
'certificate': '',
'certfile': '',
@@ -50,7 +52,6 @@ DEFAULT_CONFIG = {
'custom_port': '',
'default_nick': '',
'default_muc_service': '',
- 'deterministic_nick_colors': True,
'device_id': '',
'nick_color_aliases': True,
'display_activity_notifications': False,
@@ -74,7 +75,6 @@ DEFAULT_CONFIG = {
'extract_inline_images': True,
'filter_info_messages': '',
'force_encryption': True,
- 'force_remote_bookmarks': False,
'go_to_previous_tab_on_alt_number': False,
'group_corrections': True,
'hide_exit_join': -1,
@@ -92,6 +92,8 @@ DEFAULT_CONFIG = {
'lazy_resize': True,
'log_dir': '',
'log_errors': True,
+ 'mam_sync': True,
+ 'mam_sync_limit': 2000,
'max_lines_in_memory': 2048,
'max_messages_in_memory': 2048,
'max_nick_length': 25,
@@ -133,9 +135,11 @@ DEFAULT_CONFIG = {
'show_useless_separator': True,
'status': '',
'status_message': '',
+ 'synchronise_open_rooms': True,
'theme': 'default',
'themes_dir': '',
'tmp_image_dir': '',
+ 'unique_prefix_tab_names': False,
'use_bookmarks_method': '',
'use_log': True,
'use_remote_bookmarks': True,
@@ -157,21 +161,33 @@ DEFAULT_CONFIG = {
}
-class Config(RawConfigParser):
+class PoezioConfigParser(RawConfigParser):
+ def optionxform(self, value) -> str:
+ return str(value)
+
+
+class Config:
"""
load/save the config to a file
"""
- def __init__(self, file_name: Path, default=None) -> None:
- RawConfigParser.__init__(self, None)
+ configparser: PoezioConfigParser
+ file_name: Path
+ default: ConfigDict
+ default_section: str = 'Poezio'
+
+ def __init__(self, file_name: Path, default: Optional[ConfigDict] = None) -> None:
+ self.configparser = PoezioConfigParser()
# make the options case sensitive
- self.optionxform = lambda param: str(param)
self.file_name = file_name
self.read_file()
- self.default = default
+ self.default = default or {}
+
+ def optionxform(self, value):
+ return str(value)
def read_file(self):
- RawConfigParser.read(self, str(self.file_name), encoding='utf-8')
+ self.configparser.read(str(self.file_name), encoding='utf-8')
# Check config integrity and fix it if it’s wrong
# only when the object is the main config
if self.__class__ is Config:
@@ -182,38 +198,62 @@ class Config(RawConfigParser):
def get(self,
option: str,
default: Optional[ConfigValue] = None,
- section=DEFSECTION) -> ConfigValue:
+ section: str = USE_DEFAULT_SECTION) -> Any:
"""
get a value from the config but return
a default value if it is not found
The type of default defines the type
returned
"""
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
if default is None:
- if self.default:
- default = self.default.get(section, {}).get(option)
- else:
- default = ''
+ default = self.default.get(section, {}).get(option, '')
+ res: Optional[ConfigValue]
try:
if isinstance(default, bool):
- res = self.getboolean(option, section)
+ res = self.configparser.getboolean(section, option)
elif isinstance(default, int):
- res = self.getint(option, section)
+ res = self.configparser.getint(section, option)
elif isinstance(default, float):
- res = self.getfloat(option, section)
+ res = self.configparser.getfloat(section, option)
else:
- res = self.getstr(option, section)
+ res = self.configparser.get(section, option)
except (NoOptionError, NoSectionError, ValueError, AttributeError):
- return default if default is not None else ''
+ return default
if res is None:
return default
return res
+ def _get_default(self, option, section):
+ if self.default:
+ return self.default.get(section, {}).get(option)
+ else:
+ return ''
+
+ def sections(self, *args, **kwargs) -> List[str]:
+ return self.configparser.sections(*args, **kwargs)
+
+ def options(self, *args, **kwargs):
+ return self.configparser.options(*args, **kwargs)
+
+ def has_option(self, *args, **kwargs) -> bool:
+ return self.configparser.has_option(*args, **kwargs)
+
+ def has_section(self, *args, **kwargs) -> bool:
+ return self.configparser.has_section(*args, **kwargs)
+
+ def add_section(self, *args, **kwargs):
+ return self.configparser.add_section(*args, **kwargs)
+
+ def remove_section(self, *args, **kwargs):
+ return self.configparser.remove_section(*args, **kwargs)
+
def get_by_tabname(self,
option,
- tabname: str,
+ tabname: JID,
fallback=True,
fallback_server=True,
default=''):
@@ -223,15 +263,12 @@ class Config(RawConfigParser):
in the section, we search for the global option if fallback is
True. And we return `default` as a fallback as a last resort.
"""
- from slixmpp import JID
- if isinstance(tabname, JID):
- tabname = tabname.full
if self.default and (not default) and fallback:
- default = self.default.get(DEFSECTION, {}).get(option, '')
+ default = self.default.get(self.default_section, {}).get(option, '')
if tabname in self.sections():
if option in self.options(tabname):
# We go the tab-specific option
- return self.get(option, default, tabname)
+ return self.get(option, default, tabname.full)
if fallback_server:
return self.get_by_servname(tabname, option, default, fallback)
if fallback:
@@ -243,7 +280,10 @@ class Config(RawConfigParser):
"""
Try to get the value of an option for a server
"""
- server = safeJID(jid).server
+ try:
+ server = JID(jid).server
+ except InvalidJID:
+ server = ''
if server:
server = '@' + server
if server in self.sections() and option in self.options(server):
@@ -252,11 +292,13 @@ class Config(RawConfigParser):
return self.get(option, default)
return default
- def __get(self, option, section=DEFSECTION, **kwargs):
+ def __get(self, option, section=USE_DEFAULT_SECTION, **kwargs):
"""
facility for RawConfigParser.get
"""
- return RawConfigParser.get(self, section, option, **kwargs)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ return self.configparser.get(section, option, **kwargs)
def _get(self, section, conv, option, **kwargs):
"""
@@ -264,29 +306,54 @@ class Config(RawConfigParser):
"""
return conv(self.__get(option, section, **kwargs))
- def getstr(self, option, section=DEFSECTION):
+ def getstr(self, option, section=USE_DEFAULT_SECTION) -> str:
"""
get a value and returns it as a string
"""
- return self.__get(option, section)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ try:
+ return self.configparser.get(section, option)
+ except (NoOptionError, NoSectionError, ValueError, AttributeError):
+ return cast(str, self._get_default(option, section))
- def getint(self, option, section=DEFSECTION):
+ def getint(self, option, section=USE_DEFAULT_SECTION) -> int:
"""
get a value and returns it as an int
"""
- return RawConfigParser.getint(self, section, option)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ try:
+ return self.configparser.getint(section, option)
+ except (NoOptionError, NoSectionError, ValueError, AttributeError):
+ return cast(int, self._get_default(option, section))
- def getfloat(self, option, section=DEFSECTION):
+ def getfloat(self, option, section=USE_DEFAULT_SECTION) -> float:
"""
get a value and returns it as a float
"""
- return RawConfigParser.getfloat(self, section, option)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ try:
+ return self.configparser.getfloat(section, option)
+ except (NoOptionError, NoSectionError, ValueError, AttributeError):
+ return cast(float, self._get_default(option, section))
- def getboolean(self, option, section=DEFSECTION):
+ def getbool(self, option, section=USE_DEFAULT_SECTION) -> bool:
"""
get a value and returns it as a boolean
"""
- return RawConfigParser.getboolean(self, section, option)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ try:
+ return self.configparser.getboolean(section, option)
+ except (NoOptionError, NoSectionError, ValueError, AttributeError):
+ return cast(bool, self._get_default(option, section))
+
+ def getlist(self, option, section=USE_DEFAULT_SECTION) -> List[str]:
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ return self.getstr(option, section).split(':')
def write_in_file(self, section: str, option: str,
value: ConfigValue) -> bool:
@@ -382,8 +449,7 @@ class Config(RawConfigParser):
if file_ok(self.file_name):
try:
with self.file_name.open('r', encoding='utf-8') as df:
- lines_before = [line.strip()
- for line in df] # type: List[str]
+ lines_before: List[str] = [line.strip() for line in df]
except OSError:
log.error(
'Unable to read the config file %s',
@@ -393,7 +459,7 @@ class Config(RawConfigParser):
else:
lines_before = []
- sections = {} # type: Dict[str, List[int]]
+ sections: Dict[str, List[int]] = {}
duplicate_section = False
current_section = ''
current_line = 0
@@ -420,7 +486,7 @@ class Config(RawConfigParser):
return (sections, lines_before)
def set_and_save(self, option: str, value: ConfigValue,
- section=DEFSECTION) -> Tuple[str, str]:
+ section=USE_DEFAULT_SECTION) -> Tuple[str, str]:
"""
set the value in the configuration then save it
to the file
@@ -428,10 +494,12 @@ class Config(RawConfigParser):
# Special case for a 'toggle' value. We take the current value
# and set the opposite. Warning if the no current value exists
# or it is not a bool.
- if value == "toggle":
- current = self.get(option, "", section)
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
+ if isinstance(value, str) and value == "toggle":
+ current = self.getbool(option, section)
if isinstance(current, bool):
- value = str(not current)
+ value = str(not current).lower()
else:
if current.lower() == "false":
value = "true"
@@ -442,11 +510,12 @@ class Config(RawConfigParser):
'Could not toggle option: %s.'
' Current value is %s.' % (option, current or "empty"),
'Warning')
+ value = str(value)
if self.has_section(section):
- RawConfigParser.set(self, section, option, value)
+ self.configparser.set(section, option, value)
else:
self.add_section(section)
- RawConfigParser.set(self, section, option, value)
+ self.configparser.set(section, option, value)
if not self.write_in_file(section, option, value):
return ('Unable to write in the config file', 'Error')
if isinstance(option, str) and 'password' in option and 'eval_password' not in option:
@@ -454,41 +523,47 @@ class Config(RawConfigParser):
return ("%s=%s" % (option, value), 'Info')
def remove_and_save(self, option: str,
- section=DEFSECTION) -> Tuple[str, str]:
+ section=USE_DEFAULT_SECTION) -> Tuple[str, str]:
"""
Remove an option and then save it the config file
"""
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
if self.has_section(section):
- RawConfigParser.remove_option(self, section, option)
+ self.configparser.remove_option(section, option)
if not self.remove_in_file(section, option):
return ('Unable to save the config file', 'Error')
return ('Option %s deleted' % option, 'Info')
- def silent_set(self, option: str, value: ConfigValue, section=DEFSECTION):
+ def silent_set(self, option: str, value: ConfigValue, section=USE_DEFAULT_SECTION):
"""
Set a value, save, and return True on success and False on failure
"""
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
if self.has_section(section):
- RawConfigParser.set(self, section, option, value)
+ self.configparser.set(section, option, str(value))
else:
self.add_section(section)
- RawConfigParser.set(self, section, option, value)
- return self.write_in_file(section, option, value)
+ self.configparser.set(section, option, str(value))
+ return self.write_in_file(section, option, str(value))
- def set(self, option: str, value: ConfigValue, section=DEFSECTION):
+ def set(self, option: str, value: ConfigValue, section=USE_DEFAULT_SECTION):
"""
Set the value of an option temporarily
"""
+ if section == USE_DEFAULT_SECTION:
+ section = self.default_section
try:
- RawConfigParser.set(self, section, option, value)
+ self.configparser.set(section, option, str(value))
except NoSectionError:
pass
- def to_dict(self) -> Dict[str, Dict[str, ConfigValue]]:
+ def to_dict(self) -> Dict[str, Dict[str, Optional[ConfigValue]]]:
"""
Returns a dict of the form {section: {option: value, option: value}, …}
"""
- res = {} # Dict[str, Dict[str, ConfigValue]]
+ res: Dict[str, Dict[str, Optional[ConfigValue]]] = {}
for section in self.sections():
res[section] = {}
for option in self.options(section):
@@ -522,10 +597,10 @@ def file_ok(filepath: Path) -> bool:
return bool(val)
-def get_image_cache() -> Path:
+def get_image_cache() -> Optional[Path]:
if not config.get('extract_inline_images'):
return None
- tmp_dir = config.get('tmp_image_dir')
+ tmp_dir = config.getstr('tmp_image_dir')
if tmp_dir:
return Path(tmp_dir)
return xdg.CACHE_HOME / 'images'
@@ -564,43 +639,11 @@ def check_config():
print(' \033[31m%s\033[0m' % option)
-def run_cmdline_args():
- "Parse the command line arguments"
- global options
- options = parse_args(xdg.CONFIG_HOME)
-
- # Copy a default file if none exists
- if not options.filename.is_file():
- try:
- options.filename.parent.mkdir(parents=True, exist_ok=True)
- except OSError as e:
- sys.stderr.write(
- 'Poezio was unable to create the config directory: %s\n' % e)
- sys.exit(1)
- default = Path(__file__).parent / '..' / 'data' / 'default_config.cfg'
- other = Path(
- pkg_resources.resource_filename('poezio', 'default_config.cfg'))
- if default.is_file():
- copy2(str(default), str(options.filename))
- elif other.is_file():
- copy2(str(other), str(options.filename))
-
- # Inside the nixstore and possibly other distributions, the reference
- # file is readonly, so is the copy.
- # Make it writable by the user who just created it.
- if options.filename.exists():
- options.filename.chmod(options.filename.stat().st_mode
- | stat.S_IWUSR)
-
- global firstrun
- firstrun = True
-
-
-def create_global_config():
+def create_global_config(filename):
"Create the global config object, or crash"
try:
global config
- config = Config(options.filename, DEFAULT_CONFIG)
+ config = Config(filename, DEFAULT_CONFIG)
except:
import traceback
sys.stderr.write('Poezio was unable to read or'
@@ -609,11 +652,13 @@ def create_global_config():
sys.exit(1)
-def setup_logging():
+def setup_logging(debug_file=''):
"Change the logging config according to the cmdline options and config"
global LOG_DIR
LOG_DIR = config.get('log_dir')
LOG_DIR = Path(LOG_DIR).expanduser() if LOG_DIR else xdg.DATA_HOME / 'logs'
+ from copy import deepcopy
+ logging_config = deepcopy(LOGGING_CONFIG)
if config.get('log_errors'):
try:
LOG_DIR.mkdir(parents=True, exist_ok=True)
@@ -621,8 +666,8 @@ def setup_logging():
# We can’t really log any error here, because logging isn’t setup yet.
pass
else:
- LOGGING_CONFIG['root']['handlers'].append('error')
- LOGGING_CONFIG['handlers']['error'] = {
+ logging_config['root']['handlers'].append('error')
+ logging_config['handlers']['error'] = {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': str(LOG_DIR / 'errors.log'),
@@ -630,37 +675,26 @@ def setup_logging():
}
logging.disable(logging.WARNING)
- if options.debug:
- LOGGING_CONFIG['root']['handlers'].append('debug')
- LOGGING_CONFIG['handlers']['debug'] = {
+ if debug_file:
+ logging_config['root']['handlers'].append('debug')
+ logging_config['handlers']['debug'] = {
'level': 'DEBUG',
'class': 'logging.FileHandler',
- 'filename': options.debug,
+ 'filename': debug_file,
'formatter': 'simple',
}
logging.disable(logging.NOTSET)
- if LOGGING_CONFIG['root']['handlers']:
- logging.config.dictConfig(LOGGING_CONFIG)
+ if logging_config['root']['handlers']:
+ logging.config.dictConfig(logging_config)
else:
logging.disable(logging.ERROR)
logging.basicConfig(level=logging.CRITICAL)
- global log
- log = logging.getLogger(__name__)
-
-
-def post_logging_setup():
- # common imports slixmpp, which creates then its loggers, so
- # it needs to be after logger configuration
- from poezio.common import safeJID as JID
- global safeJID
- safeJID = JID
-
LOGGING_CONFIG = {
'version': 1,
- 'disable_existing_loggers': True,
+ 'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(asctime)s %(levelname)s:%(module)s:%(message)s'
@@ -674,21 +708,8 @@ LOGGING_CONFIG = {
}
}
-# True if this is the first run, in this case we will display
-# some help in the info buffer
-firstrun = False
-
-# Global config object. Is setup in poezio.py
-config = None # type: Config
-
-# The logger object for this module
-log = None # type: Optional[logging.Logger]
-
-# The command-line options
-options = None
-
-# delayed import from common.py
-safeJID = None # type: Optional[Callable]
+# Global config object. Is setup for real in poezio.py
+config = Config(Path('/dev/null'))
# the global log dir
LOG_DIR = Path()