Skip to content

Commit

Permalink
Multiple configuration files
Browse files Browse the repository at this point in the history
  • Loading branch information
kalgasnik committed Mar 19, 2015
1 parent 30d133d commit bf26da3
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 51 deletions.
183 changes: 183 additions & 0 deletions lightdm_gtk_greeter_settings/Config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# LightDM GTK Greeter Settings
# Copyright (C) 2015 Andrew P. <[email protected]>
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.

import configparser
import os
from collections import OrderedDict
from glob import iglob


class Config:

class ConfigGroup:

def __init__(self, config):
self._config = config
self._items = OrderedDict()

def __iter__(self):
return iter(self._items)

def __contains__(self, item):
return item in self._items

def __getitem__(self, item):
values = self._items.get(item)
return values[-1][1] if values else None

def __setitem__(self, item, value):
if isinstance(value, tuple):
value, default = value
else:
default = None

values = self._items.get(item)
if values and values[-1][0] == self._config._output_path:
if default is not None and value == default and len(values) == 1:
values.clear()
else:
values[-1] = (self._config._output_path, value)
elif values is not None:
if default is None or value != default or (values and values[-1][1] != default):
values.append((self._config._output_path, value))
else:
if default is None or value != default:
self._items[item] = [(self._config._output_path, value)]

def __delitem__(self, item):
values = self._items.get(item)
if values is not None:
if values and values[-1][0] == self._config._output_path:
del values[-1]
if not values:
del self._items[item]

def get_key_file(self, key):
values = self._items.get(key)
return values[-1][0] if values else None

def __init__(self, input_pathes, output_path):
self._input_pathes = tuple(input_pathes)
self._output_path = output_path
self._groups = OrderedDict()

def read(self):
files = []
for path in self._input_pathes:
if os.path.isdir(path):
files.extend(sorted(iglob(os.path.join(path, '*.conf'))))
elif os.path.exists(path):
files.append(path)
if self._output_path not in files:
files.append(self._output_path)

self._groups.clear()
for path in files:
config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)
config_file.read(path)

for groupname, values in config_file.items():
if groupname == 'DEFAULT':
continue

if groupname not in self._groups:
self._groups[groupname] = Config.ConfigGroup(self)
group = self._groups[groupname]

for key, value in values.items():
if key in group._items:
values = group._items[key]
if value is not None or values:
values.append((path, value))
elif value is not None:
group._items[key] = [(path, value)]

def write(self):
config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)

for groupname, group in self._groups.items():
config_file.add_section(groupname)
config_section = config_file[groupname]

for key, values in group._items.items():
if not values or values[-1][0] != self._output_path:
continue
if values[-1][1] is not None or len(values) > 1:
config_section[key] = values[-1][1]

with open(self._output_path, 'w') as file:
config_file.write(file)

def items(self):
return self._groups.items()

def allitems(self):
return ((g, k, items[k]) for (g, items) in self._groups.items() for k in items._items)

def add_group(self, name):
if name in self._groups:
return self._groups[name]
else:
return self._groups.setdefault(name, Config.ConfigGroup(self))

def get_key_file(self, groupname, key):
group = self._groups.get(groupname)
return group.get_key_file(key) if group is not None else None

def __iter__(self):
return iter(self._groups)

def __getitem__(self, item):
if isinstance(item, tuple):
group = self._groups.get(item[0])
return group[item[1]] if group else None
return self._groups.get(item)

def __setitem__(self, item, value):
if isinstance(item, tuple):
if not item[0] in self._groups:
self._groups = Config.ConfigGroup(self)
self._groups[item[0]][item[1]] = value

def __delitem__(self, item):
if isinstance(item, tuple):
group = self._groups.get(item[0])
if group is not None:
del group[item[1]]
return

group = self._groups.get(item)
if group is not None:
if not group:
del self._groups[item]
return

keys_to_remove = []
for key, values in group._items.items():
if values[-1][0] == self._output_path:
if len(values) == 1:
keys_to_remove.append(key)
else:
values[-1] = (self._output_path, None)
elif values:
values.append((self._output_path, None))

if len(keys_to_remove) < len(group._items):
for key in keys_to_remove:
del group._items[key]
else:
del self._groups[item]
55 changes: 33 additions & 22 deletions lightdm_gtk_greeter_settings/GtkGreeterSettingsWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@


import collections
import configparser
import os
import shlex
import sys
from glob import iglob
from functools import partialmethod
from glob import iglob
from itertools import chain
from locale import gettext as _

from gi.repository import (
Gdk,
GLib,
Gtk)
from gi.repository import Pango
from gi.repository.GObject import markup_escape_text as escape_markup

from lightdm_gtk_greeter_settings import (
Config,
helpers,
IconEntry,
IndicatorsEntry,
Expand Down Expand Up @@ -138,8 +139,17 @@ def init_window(self):
group.entry_added.connect(self.on_entry_added)
group.entry_removed.connect(self.on_entry_removed)

self._config_path = helpers.get_config_path()
self._allow_edit = self._has_access_to_write(self._config_path)
config_pathes = []
config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
for p in GLib.get_system_data_dirs())
config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
for p in GLib.get_system_config_dirs())
config_pathes.append(os.path.join(os.path.dirname(helpers.get_config_path()),
'lightdm-gtk-greeter.conf.d'))

self._config = Config.Config(config_pathes, helpers.get_config_path())

self._allow_edit = self._has_access_to_write(helpers.get_config_path())
self._widgets.apply.props.visible = self._allow_edit

if not self._allow_edit:
Expand All @@ -151,7 +161,7 @@ def init_window(self):
secondary_text=_(
'It seems that you don\'t have permissions to write to '
'file:\n{path}\n\nTry to run this program using "sudo" '
'or "pkexec"').format(path=self._config_path),
'or "pkexec"').format(path=helpers.get_config_path()),
message_type=Gtk.MessageType.WARNING)

if self.mode == WindowMode.Embedded:
Expand All @@ -176,13 +186,12 @@ def init_window(self):

self.set_titlebar(header)

self._config = configparser.RawConfigParser(strict=False)
self._read()

def _has_access_to_write(self, path):
if os.path.exists(path) and os.access(self._config_path, os.W_OK):
if os.path.exists(path) and os.access(helpers.get_config_path(), os.W_OK):
return True
return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)
return os.access(os.path.dirname(helpers.get_config_path()), os.W_OK | os.X_OK)

def _set_message(self, message, type_=Gtk.MessageType.INFO):
if not message:
Expand All @@ -193,17 +202,7 @@ def _set_message(self, message, type_=Gtk.MessageType.INFO):
self._widgets.infobar.show()

def _read(self):
self._config.clear()
try:
if not self._config.read(self._config_path) and \
self.mode != WindowMode.Embedded:
helpers.show_message(text=_('Failed to read configuration file: {path}')
.format(path=self._config_path),
message_type=Gtk.MessageType.ERROR)
except (configparser.DuplicateSectionError,
configparser.MissingSectionHeaderError):
pass

self._config.read()
self._changed_entries = None

for group in self._groups:
Expand All @@ -226,8 +225,7 @@ def _write(self):
self._widgets.apply.props.sensitive = False

try:
with open(self._config_path, 'w') as file:
self._config.write(file)
self._config.write()
except OSError as e:
helpers.show_message(e, Gtk.MessageType.ERROR)

Expand Down Expand Up @@ -321,6 +319,7 @@ def new_item(activate=None, width=90):
class EntryMenu:
menu = Gtk.Menu()
value = new_item()
file = new_item()
error_separator = Gtk.SeparatorMenuItem()
error = new_item()
error_action = new_item(self.on_entry_fix_clicked)
Expand All @@ -329,6 +328,7 @@ class EntryMenu:
default = new_item(self.on_entry_reset_clicked)

menu.append(value)
menu.append(file)
menu.append(error_separator)
menu.append(error)
menu.append(error_action)
Expand Down Expand Up @@ -356,6 +356,17 @@ def format_value(value=None, enabled=True):
key=key,
value=format_value(value=entry.value, enabled=entry.enabled))

key_file = None
if entry not in self._changed_entries:
key_file = self._config.get_key_file(group.name, key)
if key_file and key_file == helpers.get_config_path():
key_file = None
elif key_file:
menu.file.props.label = _('Value defined in file: {path}')\
.format(path=escape_markup(key_file))
menu.file.set_tooltip_text(key_file)
menu.file.props.visible = key_file is not None

error = entry.error
error_action = None
if error:
Expand All @@ -367,7 +378,7 @@ def format_value(value=None, enabled=True):
menu.error_action.props.label = label or ''
if error_action:
menu.error_action._fix_entry_data = entry, error_action
menu.error.set_label(error)
menu.error.set_label(escape_markup(error))

menu.error.props.visible = error is not None
menu.error_action.props.visible = error_action is not None
Expand Down
25 changes: 14 additions & 11 deletions lightdm_gtk_greeter_settings/MonitorsGroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,31 @@ def __init__(self, widgets, defaults_callback=None):

def read(self, config):
self._entries.clear()
for name, section in config.items():
for name, group in config.items():
if not name.startswith(self.GROUP_PREFIX):
continue
name = name[len(self.GROUP_PREFIX):].strip()
entry = MonitorEntry(self._widgets)
entry['background'] = section.get('background', None)
entry['user-background'] = bool2string(section.getboolean('user-background', None), 1)
entry['laptop'] = bool2string(section.getboolean('laptop', None), True)
entry['background'] = group['background']
entry['user-background'] = bool2string(group['user-background'], True)
entry['laptop'] = bool2string(group['laptop'], True)
self._entries[name] = entry
self.entry_added.emit(entry, name)

def write(self, config):
for name in config.sections():
if name.startswith(self.GROUP_PREFIX):
config.remove_section(name)
groups = set(name for name, __ in self._entries.items())
groups_to_remove = tuple(name for name in config
if (name.startswith(self.GROUP_PREFIX) and
name[len(self.GROUP_PREFIX):].strip() not in groups))

for name, entry in self._entries.items():
section = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
config.add_section(section)
groupname = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
group = config.add_group(groupname)
for key, value in entry:
if value is not None:
config.set(section, key, value)
group[key] = value

for name in groups_to_remove:
del config[name]

def _on_label_link_activate(self, label, uri):
if not self._dialog:
Expand Down
Loading

0 comments on commit bf26da3

Please sign in to comment.