Start working on different per-URL storage
This commit is contained in:
parent
5e50824042
commit
8551288efb
@ -26,7 +26,7 @@ import functools
|
|||||||
import attr
|
import attr
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||||
|
|
||||||
from qutebrowser.config import configdata, configexc
|
from qutebrowser.config import configdata, configexc, configutils
|
||||||
from qutebrowser.utils import utils, log, jinja
|
from qutebrowser.utils import utils, log, jinja
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
||||||
@ -229,25 +229,12 @@ class KeyConfig:
|
|||||||
self._config.update_mutables(save_yaml=save_yaml)
|
self._config.update_mutables(save_yaml=save_yaml)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
|
||||||
class PerUrlSettings:
|
|
||||||
|
|
||||||
"""A simple container with an URL pattern and settings for it."""
|
|
||||||
|
|
||||||
pattern = attr.ib()
|
|
||||||
values = attr.ib(default=attr.Factory(dict))
|
|
||||||
|
|
||||||
|
|
||||||
class Config(QObject):
|
class Config(QObject):
|
||||||
|
|
||||||
"""Main config object.
|
"""Main config object.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
_values: A dict mapping setting names to their values.
|
_values: A dict mapping setting names to configutils.Values objects.
|
||||||
_per_url_values: A mapping from UrlPattern objects to PerUrlSetting
|
|
||||||
instances. Note that dict lookup is currently only
|
|
||||||
useful for finding the pattern when adding values, not
|
|
||||||
for getting values.
|
|
||||||
_mutables: A dictionary of mutable objects to be checked for changes.
|
_mutables: A dictionary of mutable objects to be checked for changes.
|
||||||
_yaml: A YamlConfig object or None.
|
_yaml: A YamlConfig object or None.
|
||||||
|
|
||||||
@ -261,18 +248,14 @@ class Config(QObject):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.changed.connect(_render_stylesheet.cache_clear)
|
self.changed.connect(_render_stylesheet.cache_clear)
|
||||||
self._values = {}
|
self._values = {}
|
||||||
self._per_url_values = {}
|
|
||||||
self._mutables = {}
|
self._mutables = {}
|
||||||
self._yaml = yaml_config
|
self._yaml = yaml_config
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""Iterate over UrlPattern, Option, value tuples."""
|
"""Iterate over Option, ScopedValue tuples."""
|
||||||
for name, value in sorted(self._values.items()):
|
for name, values in sorted(self._values.items()):
|
||||||
yield (None, self.get_opt(name), value)
|
for scoped in values:
|
||||||
|
yield self.get_opt(name), scoped
|
||||||
for pattern, options in sorted(self._per_url_values.items()):
|
|
||||||
for name, value in sorted(options.values.items()):
|
|
||||||
yield (pattern, self.get_opt(name), value)
|
|
||||||
|
|
||||||
def init_save_manager(self, save_manager):
|
def init_save_manager(self, save_manager):
|
||||||
"""Make sure the config gets saved properly.
|
"""Make sure the config gets saved properly.
|
||||||
@ -282,40 +265,6 @@ class Config(QObject):
|
|||||||
"""
|
"""
|
||||||
self._yaml.init_save_manager(save_manager)
|
self._yaml.init_save_manager(save_manager)
|
||||||
|
|
||||||
def _get_values(self, pattern=None, *, create=False):
|
|
||||||
"""Get the appropriate _values instance for the given pattern.
|
|
||||||
|
|
||||||
With create=True, create a new one instead of returning an empty dict.
|
|
||||||
"""
|
|
||||||
if pattern is None:
|
|
||||||
return self._values
|
|
||||||
elif pattern in self._per_url_values:
|
|
||||||
return self._per_url_values[pattern].values
|
|
||||||
elif create:
|
|
||||||
settings = PerUrlSettings(pattern)
|
|
||||||
self._per_url_values[pattern] = settings
|
|
||||||
return settings.values
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def _get_values_for_url(self, url):
|
|
||||||
"""Get a temporary values container which merges all matching values.
|
|
||||||
|
|
||||||
Note that this does *not* include global values.
|
|
||||||
|
|
||||||
Currently, this iterates linearly over all patterns. This could probably
|
|
||||||
be optimized by storing patterns based on their scheme/host/port and
|
|
||||||
then searching all possible matches in a dict before doing a full match.
|
|
||||||
"""
|
|
||||||
# FIXME We could avoid the copy if there's no per-url match.
|
|
||||||
# values = self._values.copy()
|
|
||||||
values = {}
|
|
||||||
# FIXME:conf what order?
|
|
||||||
for options in self._per_url_values.values():
|
|
||||||
if options.pattern.matches(url):
|
|
||||||
values.update(options.values)
|
|
||||||
return values
|
|
||||||
|
|
||||||
def _set_value(self, opt, value, pattern=None):
|
def _set_value(self, opt, value, pattern=None):
|
||||||
"""Set the given option to the given value."""
|
"""Set the given option to the given value."""
|
||||||
if not isinstance(objects.backend, objects.NoBackend):
|
if not isinstance(objects.backend, objects.NoBackend):
|
||||||
@ -323,8 +272,8 @@ class Config(QObject):
|
|||||||
raise configexc.BackendError(opt.name, objects.backend)
|
raise configexc.BackendError(opt.name, objects.backend)
|
||||||
|
|
||||||
opt.typ.to_py(value) # for validation
|
opt.typ.to_py(value) # for validation
|
||||||
values = self._get_values(pattern, create=True)
|
scoped = configutils.ScopedValue(opt.typ.from_obj(value), pattern)
|
||||||
values[opt.name] = opt.typ.from_obj(value)
|
self._values[opt.name].add(scoped)
|
||||||
|
|
||||||
self.changed.emit(opt.name)
|
self.changed.emit(opt.name)
|
||||||
log.config.debug("Config option changed: {} = {}".format(
|
log.config.debug("Config option changed: {} = {}".format(
|
||||||
@ -348,20 +297,18 @@ class Config(QObject):
|
|||||||
name, deleted=deleted, renamed=renamed)
|
name, deleted=deleted, renamed=renamed)
|
||||||
raise exception from None
|
raise exception from None
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name, url=None):
|
||||||
"""Get the given setting converted for Python code."""
|
"""Get the given setting converted for Python code."""
|
||||||
opt = self.get_opt(name)
|
opt = self.get_opt(name)
|
||||||
obj = self.get_obj(name, mutable=False)
|
obj = self.get_obj(name, mutable=False, url=url)
|
||||||
return opt.typ.to_py(obj)
|
return opt.typ.to_py(obj)
|
||||||
|
|
||||||
def get_obj(self, name, *, mutable=True, pattern=None):
|
def get_obj(self, name, *, mutable=True, url=None):
|
||||||
"""Get the given setting as object (for YAML/config.py).
|
"""Get the given setting as object (for YAML/config.py).
|
||||||
|
|
||||||
If mutable=True is set, watch the returned object for mutations.
|
If mutable=True is set, watch the returned object for mutations.
|
||||||
If a pattern is given, get the per-domain setting for that pattern (if
|
If a URL is given, return the value which should be used for that URL.
|
||||||
any).
|
|
||||||
"""
|
"""
|
||||||
opt = self.get_opt(name)
|
|
||||||
obj = None
|
obj = None
|
||||||
# If we allow mutation, there is a chance that prior mutations already
|
# If we allow mutation, there is a chance that prior mutations already
|
||||||
# entered the mutable dictionary and thus further copies are unneeded
|
# entered the mutable dictionary and thus further copies are unneeded
|
||||||
@ -371,9 +318,11 @@ class Config(QObject):
|
|||||||
# Otherwise, we return a copy of the value stored internally, so the
|
# Otherwise, we return a copy of the value stored internally, so the
|
||||||
# internal value can never be changed by mutating the object returned.
|
# internal value can never be changed by mutating the object returned.
|
||||||
else:
|
else:
|
||||||
values = self._get_values(pattern)
|
if name in self._values:
|
||||||
|
value = self._values[name].get_any(url)
|
||||||
obj = copy.deepcopy(values.get(name, opt.default))
|
else:
|
||||||
|
value = self.get_opt(name).default
|
||||||
|
obj = copy.deepcopy(value)
|
||||||
# Then we watch the returned object for changes.
|
# Then we watch the returned object for changes.
|
||||||
if isinstance(obj, (dict, list)):
|
if isinstance(obj, (dict, list)):
|
||||||
if mutable:
|
if mutable:
|
||||||
@ -383,39 +332,17 @@ class Config(QObject):
|
|||||||
assert obj.__hash__ is not None, obj
|
assert obj.__hash__ is not None, obj
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_for_url(self, name, url, *, maybe_unset=True):
|
|
||||||
"""Get the given per-url setting converted for Python code.
|
|
||||||
|
|
||||||
With maybe_unset=True, if the value isn't overridden for a given domain,
|
|
||||||
return UNSET.
|
|
||||||
|
|
||||||
With maybe_unset=False, return the global/default value instead.
|
|
||||||
"""
|
|
||||||
opt = self.get_opt(name)
|
|
||||||
obj = self._get_obj_for_url(opt, url=url, maybe_unset=maybe_unset)
|
|
||||||
return opt.typ.to_py(obj)
|
|
||||||
|
|
||||||
def _get_obj_for_url(self, opt, url, *, maybe_unset=True):
|
|
||||||
"""Get the given setting as object (for YAML/config.py).
|
|
||||||
|
|
||||||
With maybe_unset=True, if the value isn't overridden for a given domain,
|
|
||||||
return UNSET.
|
|
||||||
|
|
||||||
With maybe_unset=False, return the global/default value instead.
|
|
||||||
"""
|
|
||||||
values = self._get_values_for_url(url)
|
|
||||||
if opt in values:
|
|
||||||
return values[opt]
|
|
||||||
elif maybe_unset:
|
|
||||||
return UNSET
|
|
||||||
else:
|
|
||||||
return self.get_obj(opt.name, mutable=False)
|
|
||||||
|
|
||||||
def get_str(self, name, *, pattern=None):
|
def get_str(self, name, *, pattern=None):
|
||||||
"""Get the given setting as string."""
|
"""Get the given setting as string.
|
||||||
|
|
||||||
|
If a pattern is given, get the setting for the given pattern or
|
||||||
|
configutils.UNSET.
|
||||||
|
"""
|
||||||
opt = self.get_opt(name)
|
opt = self.get_opt(name)
|
||||||
values = self._get_values(pattern)
|
values = self._values[name]
|
||||||
value = values.get(name, opt.default)
|
value = values.get_for_pattern(pattern)
|
||||||
|
if value is configutils.UNSET:
|
||||||
|
return value
|
||||||
return opt.typ.to_str(value)
|
return opt.typ.to_str(value)
|
||||||
|
|
||||||
def set_obj(self, name, value, *, pattern=None, save_yaml=False):
|
def set_obj(self, name, value, *, pattern=None, save_yaml=False):
|
||||||
|
@ -32,7 +32,7 @@ import yaml
|
|||||||
from PyQt5.QtCore import pyqtSignal, QObject, QSettings
|
from PyQt5.QtCore import pyqtSignal, QObject, QSettings
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.config import configexc, config, configdata
|
from qutebrowser.config import configexc, config, configdata, configutils
|
||||||
from qutebrowser.utils import standarddir, utils, qtutils, log
|
from qutebrowser.utils import standarddir, utils, qtutils, log
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +88,6 @@ class YamlConfig(QObject):
|
|||||||
self._filename = os.path.join(standarddir.config(auto=True),
|
self._filename = os.path.join(standarddir.config(auto=True),
|
||||||
'autoconfig.yml')
|
'autoconfig.yml')
|
||||||
self._values = {}
|
self._values = {}
|
||||||
self._per_url_values = {}
|
|
||||||
self._dirty = None
|
self._dirty = None
|
||||||
|
|
||||||
def init_save_manager(self, save_manager):
|
def init_save_manager(self, save_manager):
|
||||||
@ -100,23 +99,8 @@ class YamlConfig(QObject):
|
|||||||
save_manager.add_saveable('yaml-config', self._save, self.changed)
|
save_manager.add_saveable('yaml-config', self._save, self.changed)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for name, value in sorted(self._values.items()):
|
for name, values in sorted(self._values.items()):
|
||||||
yield (None, name, value)
|
yield from values
|
||||||
|
|
||||||
for pattern, options in sorted(self._per_url_values.items()):
|
|
||||||
for name, value in sorted(options.values.items()):
|
|
||||||
yield (pattern, name, value)
|
|
||||||
|
|
||||||
def _get_values(self, pattern=None):
|
|
||||||
"""Get the appropriate _values instance for the given pattern."""
|
|
||||||
if pattern is None:
|
|
||||||
return self._values
|
|
||||||
elif pattern in self._per_url_values:
|
|
||||||
return self._per_url_values[pattern]
|
|
||||||
else:
|
|
||||||
values = {}
|
|
||||||
self._per_url_values[pattern] = values
|
|
||||||
return values
|
|
||||||
|
|
||||||
def _mark_changed(self):
|
def _mark_changed(self):
|
||||||
"""Mark the YAML config as changed."""
|
"""Mark the YAML config as changed."""
|
||||||
@ -129,7 +113,7 @@ class YamlConfig(QObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
data = {'config_version': self.VERSION, 'global': self._values}
|
data = {'config_version': self.VERSION, 'global': self._values}
|
||||||
for pattern, values in sorted(self._per_url_values.items()):
|
for pattern, values in sorted(self._values.items()):
|
||||||
data[str(pattern)] = values
|
data[str(pattern)] = values
|
||||||
|
|
||||||
with qtutils.savefile_open(self._filename) as f:
|
with qtutils.savefile_open(self._filename) as f:
|
||||||
@ -155,18 +139,17 @@ class YamlConfig(QObject):
|
|||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
global_obj = yaml_data.pop('global')
|
settings_obj = yaml_data.pop('settings')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
desc = configexc.ConfigErrorDesc(
|
desc = configexc.ConfigErrorDesc(
|
||||||
"While loading data",
|
"While loading data",
|
||||||
"Toplevel object does not contain 'global' key")
|
"Toplevel object does not contain 'settings' key")
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
desc = configexc.ConfigErrorDesc("While loading data",
|
desc = configexc.ConfigErrorDesc("While loading data",
|
||||||
"Toplevel object is not a dict")
|
"Toplevel object is not a dict")
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
|
|
||||||
# FIXME:conf test this
|
|
||||||
try:
|
try:
|
||||||
yaml_data.pop('config_version')
|
yaml_data.pop('config_version')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -175,22 +158,38 @@ class YamlConfig(QObject):
|
|||||||
"Toplevel object does not contain 'config_version' key")
|
"Toplevel object does not contain 'config_version' key")
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
|
|
||||||
if not isinstance(global_obj, dict):
|
self._load_settings_object(settings_obj)
|
||||||
desc = configexc.ConfigErrorDesc(
|
|
||||||
"While loading data",
|
|
||||||
"'global' object is not a dict")
|
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
|
||||||
|
|
||||||
self._values = global_obj
|
|
||||||
self._per_url_values = yaml_data
|
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
|
|
||||||
self._handle_migrations()
|
self._handle_migrations()
|
||||||
self._validate()
|
self._validate()
|
||||||
|
|
||||||
|
def _load_settings_obj(self, settings_obj):
|
||||||
|
"""Load the settings from the settings: key."""
|
||||||
|
if not isinstance(settings_obj, dict):
|
||||||
|
desc = configexc.ConfigErrorDesc(
|
||||||
|
"While loading data",
|
||||||
|
"'settings' object is not a dict")
|
||||||
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
|
|
||||||
|
# FIXME:conf test this
|
||||||
|
self._values = {}
|
||||||
|
for name, yaml_values in settings_obj.items():
|
||||||
|
values = configutils.Values(self._config.get_opt(name))
|
||||||
|
if 'global' in yaml_values:
|
||||||
|
scoped = configutils.ScopedValue(yaml_values.pop('global'))
|
||||||
|
values.add(scoped)
|
||||||
|
|
||||||
|
for pattern, value in yaml_values.items():
|
||||||
|
scoped = configutils.ScopedValue(value, pattern)
|
||||||
|
values.add(scoped)
|
||||||
|
|
||||||
|
self._values[name] = values
|
||||||
|
|
||||||
def _handle_migrations(self):
|
def _handle_migrations(self):
|
||||||
"""Migrate older configs to the newest format."""
|
"""Migrate older configs to the newest format."""
|
||||||
# FIXME:conf handle per-URL settings
|
# FIXME:conf handle per-URL settings
|
||||||
|
# FIXME:conf migrate from older format with global: key
|
||||||
|
|
||||||
# Simple renamed/deleted options
|
# Simple renamed/deleted options
|
||||||
for name in list(self._values):
|
for name in list(self._values):
|
||||||
if name in configdata.MIGRATIONS.renamed:
|
if name in configdata.MIGRATIONS.renamed:
|
||||||
|
Loading…
Reference in New Issue
Block a user