Merge pull request #84 from SunilMohanAdapa/misc2

Add option to run Plinth in non-daemon mode
This commit is contained in:
Nick Daly 2014-07-07 00:37:22 +00:00
commit 29d60fb106
43 changed files with 274 additions and 327 deletions

2
.gitignore vendored
View File

@ -4,6 +4,7 @@ current-*.tar.gz
*.tiny.css *.tiny.css
data/*.log data/*.log
data/cherrypy_sessions data/cherrypy_sessions
data/sessions
data/store.sqlite3 data/store.sqlite3
doc/*.tex doc/*.tex
doc/*.pdf doc/*.pdf
@ -27,3 +28,4 @@ data/users.sqlite3
predepend predepend
build/ build/
*.pid *.pid
.emacs.desktop*

View File

@ -67,9 +67,6 @@ specified and linked otherwise.
- share/init.d/plinth :: - - share/init.d/plinth :: -
- sudoers/plinth :: - - sudoers/plinth :: -
- templates/base.html :: [[file:templates/base.tmpl::the%20<a%20href%3D"http:/www.gnu.org/licenses/agpl.html"%20target%3D"_blank">GNU%20Affero%20General%20Public][GNU Affero General Public License, Version 3 or later]] - templates/base.html :: [[file:templates/base.tmpl::the%20<a%20href%3D"http:/www.gnu.org/licenses/agpl.html"%20target%3D"_blank">GNU%20Affero%20General%20Public][GNU Affero General Public License, Version 3 or later]]
- templates/err.html :: -
- templates/login_nav.html :: -
- templates/two_col.html :: -
- tests/actions_test.py :: - - tests/actions_test.py :: -
- tests/auth_test.py :: - - tests/auth_test.py :: -
- tests/testdata/users.sqlite3 :: - - tests/testdata/users.sqlite3 :: -

90
cfg.py
View File

@ -4,37 +4,65 @@ import os
import ConfigParser import ConfigParser
from ConfigParser import SafeConfigParser from ConfigParser import SafeConfigParser
def get_item(parser, section, name): product_name = None
try: box_name = None
return parser.get(section, name) root = None
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): file_root = None
print ("Configuration does not contain the {}.{} option.".format( python_root = None
section, name)) data_dir = None
raise store_file = None
user_db = None
parser = SafeConfigParser( status_log_file = None
defaults={ access_log_file = None
'root':os.path.dirname(os.path.realpath(__file__)), pidfile = None
}) host = None
parser.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'plinth.config')) port = None
debug = False
product_name = get_item(parser, 'Name', 'product_name') no_daemon = False
box_name = get_item(parser, 'Name', 'box_name') session_key = '_username'
root = get_item(parser, 'Path', 'root')
file_root = get_item(parser, 'Path', 'file_root')
python_root = get_item(parser, 'Path', 'python_root')
data_dir = get_item(parser, 'Path', 'data_dir')
store_file = get_item(parser, 'Path', 'store_file')
user_db = get_item(parser, 'Path', 'user_db')
status_log_file = get_item(parser, 'Path', 'status_log_file')
access_log_file = get_item(parser, 'Path', 'access_log_file')
pidfile = get_item(parser, 'Path', 'pidfile')
host = get_item(parser, 'Network', 'host')
port = int(get_item(parser, 'Network', 'port'))
main_menu = Menu() main_menu = Menu()
if store_file.endswith(".sqlite3"):
store_file = os.path.splitext(store_file)[0] def read():
if user_db.endswith(".sqlite3"): """Read configuration"""
user_db = os.path.splitext(user_db)[0] directory = os.path.dirname(os.path.realpath(__file__))
parser = SafeConfigParser(
defaults={
'root': directory,
})
parser.read(os.path.join(directory, 'plinth.config'))
config_items = {('Name', 'product_name'),
('Name', 'box_name'),
('Path', 'root'),
('Path', 'file_root'),
('Path', 'python_root'),
('Path', 'data_dir'),
('Path', 'store_file'),
('Path', 'user_db'),
('Path', 'status_log_file'),
('Path', 'access_log_file'),
('Path', 'pidfile'),
('Network', 'host'),
('Network', 'port')}
for section, name in config_items:
try:
value = parser.get(section, name)
globals()[name] = value
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
print ('Configuration does not contain the {}.{} option.'
.format(section, name))
raise
global port # pylint: disable-msg=W0603
port = int(port)
global store_file # pylint: disable-msg=W0603
if store_file.endswith(".sqlite3"):
store_file = os.path.splitext(store_file)[0]
global user_db # pylint: disable-msg=W0603
if user_db.endswith(".sqlite3"):
user_db = os.path.splitext(user_db)[0]

View File

@ -2,9 +2,13 @@ import cherrypy
import inspect import inspect
import cfg import cfg
cherrypy.log.error_file = cfg.status_log_file
cherrypy.log.access_file = cfg.access_log_file def init():
cherrypy.log.screen = False """Initialize logging"""
cherrypy.log.error_file = cfg.status_log_file
cherrypy.log.access_file = cfg.access_log_file
if not cfg.no_daemon:
cherrypy.log.screen = False
class Logger(object): class Logger(object):

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -20,6 +20,7 @@ Plinth module for configuring timezone, hostname etc.
""" """
from django import forms from django import forms
from django.contrib import messages
from django.core import validators from django.core import validators
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from gettext import gettext as _ from gettext import gettext as _
@ -100,14 +101,13 @@ def index(request):
status = get_status() status = get_status()
form = None form = None
messages = []
is_expert = cfg.users.expert(request=request) is_expert = cfg.users.expert(request=request)
if request.method == 'POST' and is_expert: if request.method == 'POST' and is_expert:
form = ConfigurationForm(request.POST, prefix='configuration') form = ConfigurationForm(request.POST, prefix='configuration')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(status, form.cleaned_data, messages) _apply_changes(request, status, form.cleaned_data)
status = get_status() status = get_status()
form = ConfigurationForm(initial=status, form = ConfigurationForm(initial=status,
prefix='configuration') prefix='configuration')
@ -117,7 +117,6 @@ def index(request):
return TemplateResponse(request, 'config.html', return TemplateResponse(request, 'config.html',
{'title': _('General Configuration'), {'title': _('General Configuration'),
'form': form, 'form': form,
'messages_': messages,
'is_expert': is_expert}) 'is_expert': is_expert})
@ -127,27 +126,27 @@ def get_status():
'time_zone': util.slurp('/etc/timezone').rstrip()} 'time_zone': util.slurp('/etc/timezone').rstrip()}
def _apply_changes(old_status, new_status, messages): def _apply_changes(request, old_status, new_status):
"""Apply the form changes""" """Apply the form changes"""
if old_status['hostname'] != new_status['hostname']: if old_status['hostname'] != new_status['hostname']:
if not set_hostname(new_status['hostname']): if not set_hostname(new_status['hostname']):
messages.append(('error', _('Setting hostname failed'))) messages.error(request, _('Setting hostname failed'))
else: else:
messages.append(('success', _('Hostname set'))) messages.success(request, _('Hostname set'))
else: else:
messages.append(('info', _('Hostname is unchanged'))) messages.info(request, _('Hostname is unchanged'))
if old_status['time_zone'] != new_status['time_zone']: if old_status['time_zone'] != new_status['time_zone']:
output, error = actions.superuser_run('timezone-change', output, error = actions.superuser_run('timezone-change',
[new_status['time_zone']]) [new_status['time_zone']])
del output # Unused del output # Unused
if error: if error:
messages.append(('error', messages.error(request,
_('Error setting time zone - %s') % error)) _('Error setting time zone - %s') % error)
else: else:
messages.append(('success', _('Time zone set'))) messages.success(request, _('Time zone set'))
else: else:
messages.append(('info', _('Time zone is unchanged'))) messages.info(request, _('Time zone is unchanged'))
def set_hostname(hostname): def set_hostname(hostname):

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -24,8 +24,6 @@
{% if is_expert %} {% if is_expert %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import messages
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from gettext import gettext as _ from gettext import gettext as _
@ -24,13 +25,12 @@ def index(request):
status = get_status(request) status = get_status(request)
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = ExpertsForm(request.POST, prefix='experts') form = ExpertsForm(request.POST, prefix='experts')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(request, form.cleaned_data, messages) _apply_changes(request, form.cleaned_data)
status = get_status(request) status = get_status(request)
form = ExpertsForm(initial=status, prefix='experts') form = ExpertsForm(initial=status, prefix='experts')
else: else:
@ -38,8 +38,7 @@ def index(request):
return TemplateResponse(request, 'expert_mode.html', return TemplateResponse(request, 'expert_mode.html',
{'title': _('Expert Mode'), {'title': _('Expert Mode'),
'form': form, 'form': form})
'messages_': messages})
def get_status(request): def get_status(request):
@ -47,20 +46,20 @@ def get_status(request):
return {'expert_mode': cfg.users.expert(request=request)} return {'expert_mode': cfg.users.expert(request=request)}
def _apply_changes(request, new_status, messages): def _apply_changes(request, new_status):
"""Apply expert mode configuration""" """Apply expert mode configuration"""
message = ('info', _('Settings unchanged')) message = (messages.info, _('Settings unchanged'))
user = cfg.users.current(request=request) user = cfg.users.current(request=request)
if new_status['expert_mode']: if new_status['expert_mode']:
if not 'expert' in user['groups']: if not 'expert' in user['groups']:
user['groups'].append('expert') user['groups'].append('expert')
message = ('success', _('Expert mode enabled')) message = (messages.success, _('Expert mode enabled'))
else: else:
if 'expert' in user['groups']: if 'expert' in user['groups']:
user['groups'].remove('expert') user['groups'].remove('expert')
message = ('success', _('Expert mode disabled')) message = (messages.success, _('Expert mode disabled'))
cfg.users.set(user['username'], user) cfg.users.set(user['username'], user)
messages.append(message) message[0](request, message[1])

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<p>The {{ cfg.box_name }} can be administered in two modes, 'basic' <p>The {{ cfg.box_name }} can be administered in two modes, 'basic'
and 'expert'. Basic mode hides a lot of features and configuration and 'expert'. Basic mode hides a lot of features and configuration
options that most users will never need to think about. Expert mode options that most users will never need to think about. Expert mode

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -19,6 +19,7 @@ The Plinth first-connection process has several stages:
""" """
from django import forms from django import forms
from django.contrib import messages
from django.core import validators from django.core import validators
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -101,13 +102,12 @@ def state0(request):
status = get_state0() status = get_state0()
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = State0Form(request.POST, prefix='firstboot') form = State0Form(request.POST, prefix='firstboot')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
success = _apply_state0(status, form.cleaned_data, messages) success = _apply_state0(request, status, form.cleaned_data)
if success: if success:
# Everything is good, permanently mark and move to page 2 # Everything is good, permanently mark and move to page 2
@ -119,8 +119,7 @@ def state0(request):
return TemplateResponse(request, 'firstboot_state0.html', return TemplateResponse(request, 'firstboot_state0.html',
{'title': _('First Boot!'), {'title': _('First Boot!'),
'form': form, 'form': form})
'messages_': messages})
def get_state0(): def get_state0():
@ -131,7 +130,7 @@ def get_state0():
'box_key': database.get('box_key', None)} 'box_key': database.get('box_key', None)}
def _apply_state0(old_state, new_state, messages): def _apply_state0(request, old_state, new_state):
"""Apply changes in state 0 form""" """Apply changes in state 0 form"""
success = True success = True
with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \ with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \
@ -149,11 +148,11 @@ def _apply_state0(old_state, new_state, messages):
error = add_user(new_state['username'], new_state['password'], error = add_user(new_state['username'], new_state['password'],
'First user, please change', '', True) 'First user, please change', '', True)
if error: if error:
messages.append( messages.error(
('error', _('User account creation failed: %s') % error)) request, _('User account creation failed: %s') % error)
success = False success = False
else: else:
messages.append(('success', _('User account created'))) messages.success(request, _('User account created'))
return success return success

View File

@ -24,8 +24,6 @@
<h2>Welcome to Your FreedomBox!</h2> <h2>Welcome to Your FreedomBox!</h2>
{% include 'messages.html' %}
<p>Welcome. It looks like this FreedomBox isn't set up yet. We'll <p>Welcome. It looks like this FreedomBox isn't set up yet. We'll
need to ask you a just few questions to get started.</p> need to ask you a just few questions to get started.</p>

View File

@ -36,5 +36,5 @@ def default(request, page=''):
main = input_file.read() main = input_file.read()
title = _('%s Documentation') % cfg.product_name title = _('%s Documentation') % cfg.product_name
return TemplateResponse(request, 'login_nav.html', return TemplateResponse(request, 'base.html',
{'title': title, 'main': main}) {'title': title, 'main': main})

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -6,8 +6,6 @@ from passlib.exc import PasswordSizeError
import cfg import cfg
from model import User from model import User
cfg.session_key = '_username'
def add_user(username, passphrase, name='', email='', expert=False): def add_user(username, passphrase, name='', email='', expert=False):
"""Add a new user with specified username and passphrase. """Add a new user with specified username and passphrase.

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import messages
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from gettext import gettext as _ from gettext import gettext as _
@ -34,13 +35,12 @@ def index(request):
status = get_status() status = get_status()
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = OwnCloudForm(request.POST, prefix='owncloud') form = OwnCloudForm(request.POST, prefix='owncloud')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(status, form.cleaned_data, messages) _apply_changes(request, status, form.cleaned_data)
status = get_status() status = get_status()
form = OwnCloudForm(initial=status, prefix='owncloud') form = OwnCloudForm(initial=status, prefix='owncloud')
else: else:
@ -48,8 +48,7 @@ def index(request):
return TemplateResponse(request, 'owncloud.html', return TemplateResponse(request, 'owncloud.html',
{'title': _('ownCloud'), {'title': _('ownCloud'),
'form': form, 'form': form})
'messages_': messages})
def get_status(): def get_status():
@ -61,17 +60,17 @@ def get_status():
return {'enabled': 'enable' in output.split()} return {'enabled': 'enable' in output.split()}
def _apply_changes(old_status, new_status, messages): def _apply_changes(request, old_status, new_status):
"""Apply the changes""" """Apply the changes"""
if old_status['enabled'] == new_status['enabled']: if old_status['enabled'] == new_status['enabled']:
messages.append(('info', _('Setting unchanged'))) messages.info(request, _('Setting unchanged'))
return return
if new_status['enabled']: if new_status['enabled']:
messages.append(('success', _('ownCloud enabled'))) messages.success(request, _('ownCloud enabled'))
option = 'enable' option = 'enable'
else: else:
messages.append(('success', _('ownCloud disabled'))) messages.success(request, _('ownCloud disabled'))
option = 'noenable' option = 'noenable'
actions.superuser_run('owncloud-setup', [option], async=True) actions.superuser_run('owncloud-setup', [option], async=True)

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import messages
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from gettext import gettext as _ from gettext import gettext as _
@ -51,13 +52,12 @@ def index(request):
status = get_status() status = get_status()
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = PackagesForm(request.POST, prefix='packages') form = PackagesForm(request.POST, prefix='packages')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(status, form.cleaned_data, messages) _apply_changes(request, status, form.cleaned_data)
status = get_status() status = get_status()
form = PackagesForm(initial=status, prefix='packages') form = PackagesForm(initial=status, prefix='packages')
else: else:
@ -65,8 +65,7 @@ def index(request):
return TemplateResponse(request, 'packages.html', return TemplateResponse(request, 'packages.html',
{'title': _('Add/Remove Plugins'), {'title': _('Add/Remove Plugins'),
'form': form, 'form': form})
'messages_': messages})
def get_status(): def get_status():
@ -78,7 +77,7 @@ def get_status():
for module in modules_available} for module in modules_available}
def _apply_changes(old_status, new_status, messages): def _apply_changes(request, old_status, new_status):
"""Apply form changes""" """Apply form changes"""
for field, enabled in new_status.items(): for field, enabled in new_status.items():
if not field.endswith('_enabled'): if not field.endswith('_enabled'):
@ -96,13 +95,13 @@ def _apply_changes(old_status, new_status, messages):
# TODO: need to get plinth to load the module we just # TODO: need to get plinth to load the module we just
# enabled # enabled
if error: if error:
messages.append( messages.error(
('error', _('Error enabling module - {module}').format( request, _('Error enabling module - {module}').format(
module=module))) module=module))
else: else:
messages.append( messages.success(
('success', _('Module enabled - {module}').format( request, _('Module enabled - {module}').format(
module=module))) module=module))
else: else:
output, error = actions.superuser_run( output, error = actions.superuser_run(
'module-manager', ['disable', cfg.python_root, module]) 'module-manager', ['disable', cfg.python_root, module])
@ -111,11 +110,10 @@ def _apply_changes(old_status, new_status, messages):
# TODO: need a smoother way for plinth to unload the # TODO: need a smoother way for plinth to unload the
# module # module
if error: if error:
messages.append( messages.error(
('error', request, _('Error disabling module - {module}').format(
_('Error disabling module - {module}').format( module=module))
module=module)))
else: else:
messages.append( messages.success(
('success', _('Module disabled - {module}').format( request, _('Module disabled - {module}').format(
module=module))) module=module))

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<p>aptitude purge modules</p> <p>aptitude purge modules</p>
<p>aptitude install modules</p> <p>aptitude install modules</p>

View File

@ -20,6 +20,7 @@ Plinth module for configuring PageKite service
""" """
from django import forms from django import forms
from django.contrib import messages
from django.core import validators from django.core import validators
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -104,13 +105,12 @@ def configure(request):
status = get_status() status = get_status()
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = ConfigureForm(request.POST, prefix='pagekite') form = ConfigureForm(request.POST, prefix='pagekite')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(status, form.cleaned_data, messages) _apply_changes(request, status, form.cleaned_data)
status = get_status() status = get_status()
form = ConfigureForm(initial=status, prefix='pagekite') form = ConfigureForm(initial=status, prefix='pagekite')
else: else:
@ -118,8 +118,7 @@ def configure(request):
return TemplateResponse(request, 'pagekite_configure.html', return TemplateResponse(request, 'pagekite_configure.html',
{'title': _('Configure PageKite'), {'title': _('Configure PageKite'),
'form': form, 'form': form})
'messages_': messages})
def get_status(): def get_status():
@ -154,7 +153,7 @@ def get_status():
return status return status
def _apply_changes(old_status, new_status, messages): def _apply_changes(request, old_status, new_status):
"""Apply the changes to PageKite configuration""" """Apply the changes to PageKite configuration"""
cfg.log.info('New status is - %s' % new_status) cfg.log.info('New status is - %s' % new_status)
@ -164,29 +163,28 @@ def _apply_changes(old_status, new_status, messages):
if old_status['enabled'] != new_status['enabled']: if old_status['enabled'] != new_status['enabled']:
if new_status['enabled']: if new_status['enabled']:
_run(['set-status', 'enable']) _run(['set-status', 'enable'])
messages.append(('success', _('PageKite enabled'))) messages.success(request, _('PageKite enabled'))
else: else:
_run(['set-status', 'disable']) _run(['set-status', 'disable'])
messages.append(('success', _('PageKite disabled'))) messages.success(request, _('PageKite disabled'))
if old_status['kite_name'] != new_status['kite_name'] or \ if old_status['kite_name'] != new_status['kite_name'] or \
old_status['kite_secret'] != new_status['kite_secret']: old_status['kite_secret'] != new_status['kite_secret']:
_run(['set-kite', '--kite-name', new_status['kite_name'], _run(['set-kite', '--kite-name', new_status['kite_name'],
'--kite-secret', new_status['kite_secret']]) '--kite-secret', new_status['kite_secret']])
messages.append(('success', _('Kite details set'))) messages.success(request, _('Kite details set'))
for service in ['http', 'ssh']: for service in ['http', 'ssh']:
if old_status[service + '_enabled'] != \ if old_status[service + '_enabled'] != \
new_status[service + '_enabled']: new_status[service + '_enabled']:
if new_status[service + '_enabled']: if new_status[service + '_enabled']:
_run(['set-service-status', service, 'enable']) _run(['set-service-status', service, 'enable'])
messages.append(('success', _('Service enabled: {service}') messages.success(request, _('Service enabled: {service}')
.format(service=service))) .format(service=service))
else: else:
_run(['set-service-status', service, 'disable']) _run(['set-service-status', service, 'disable'])
messages.append(('success', messages.success(request, _('Service disabled: {service}')
_('Service disabled: {service}') .format(service=service))
.format(service=service)))
if old_status != new_status: if old_status != new_status:
_run(['start']) _run(['start'])

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -31,8 +31,6 @@
{% else %} {% else %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import messages
from django.core import validators from django.core import validators
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -28,7 +29,7 @@ def index(request):
sidebar_right = render_to_string('menu_block.html', {'menu': menu}, sidebar_right = render_to_string('menu_block.html', {'menu': menu},
RequestContext(request)) RequestContext(request))
return TemplateResponse(request, 'login_nav.html', return TemplateResponse(request, 'base.html',
{'title': _('Manage Users and Groups'), {'title': _('Manage Users and Groups'),
'sidebar_right': sidebar_right}) 'sidebar_right': sidebar_right})
@ -54,36 +55,32 @@ and alphabet'),
def add(request): def add(request):
"""Serve the form""" """Serve the form"""
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = UserAddForm(request.POST, prefix='user') form = UserAddForm(request.POST, prefix='user')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_add_user(form.cleaned_data, messages) _add_user(request, form.cleaned_data)
form = UserAddForm(prefix='user') form = UserAddForm(prefix='user')
else: else:
form = UserAddForm(prefix='user') form = UserAddForm(prefix='user')
return TemplateResponse(request, 'users_add.html', return TemplateResponse(request, 'users_add.html',
{'title': _('Add User'), {'title': _('Add User'),
'form': form, 'form': form})
'messages_': messages})
def _add_user(data, messages): def _add_user(request, data):
"""Add a user""" """Add a user"""
if cfg.users.exists(data['username']): if cfg.users.exists(data['username']):
messages.append( messages.error(request, _('User "{username}" already exists').format(
('error', _('User "{username}" already exists').format( username=data['username']))
username=data['username'])))
return return
add_user(data['username'], data['password'], data['full_name'], add_user(data['username'], data['password'], data['full_name'],
data['email'], False) data['email'], False)
messages.append( messages.success(request, _('User "{username}" added').format(
('success', _('User "{username}" added').format( username=data['username']))
username=data['username'])))
class UserEditForm(forms.Form): # pylint: disable-msg=W0232 class UserEditForm(forms.Form): # pylint: disable-msg=W0232
@ -106,24 +103,22 @@ class UserEditForm(forms.Form): # pylint: disable-msg=W0232
def edit(request): def edit(request):
"""Serve the edit form""" """Serve the edit form"""
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = UserEditForm(request.POST, prefix='user') form = UserEditForm(request.POST, prefix='user')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_edit_changes(request, form.cleaned_data, messages) _apply_edit_changes(request, form.cleaned_data)
form = UserEditForm(prefix='user') form = UserEditForm(prefix='user')
else: else:
form = UserEditForm(prefix='user') form = UserEditForm(prefix='user')
return TemplateResponse(request, 'users_edit.html', return TemplateResponse(request, 'users_edit.html',
{'title': _('Edit or Delete User'), {'title': _('Edit or Delete User'),
'form': form, 'form': form})
'messages_': messages})
def _apply_edit_changes(request, data, messages): def _apply_edit_changes(request, data):
"""Apply form changes""" """Apply form changes"""
for field, value in data.items(): for field, value in data.items():
if not value: if not value:
@ -139,19 +134,17 @@ def _apply_edit_changes(request, data, messages):
(requesting_user, username)) (requesting_user, username))
if username == cfg.users.current(request=request, name=True): if username == cfg.users.current(request=request, name=True):
messages.append( messages.error(
('error', request, _('Can not delete current account - "%s"') % username)
_('Can not delete current account - "%s"') % username))
continue continue
if not cfg.users.exists(username): if not cfg.users.exists(username):
messages.append(('error', messages.error(request, _('User "%s" does not exist') % username)
_('User "%s" does not exist') % username))
continue continue
try: try:
cfg.users.remove(username) cfg.users.remove(username)
messages.append(('success', _('User "%s" deleted') % username)) messages.success(request, _('User "%s" deleted') % username)
except IOError as exception: except IOError as exception:
messages.append(('error', _('Error deleting "%s" - %s') % messages.error(request, _('Error deleting "%s" - %s') %
(username, exception))) (username, exception))

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.
@ -22,8 +22,6 @@
{% block main_block %} {% block main_block %}
{% include 'messages.html' %}
<form class="form" method="post"> <form class="form" method="post">
{% csrf_token %} {% csrf_token %}

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import messages
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
@ -42,7 +43,7 @@ def index(request):
sidebar_right = render_to_string('menu_block.html', {'menu': SIDE_MENU}, sidebar_right = render_to_string('menu_block.html', {'menu': SIDE_MENU},
RequestContext(request)) RequestContext(request))
return TemplateResponse(request, 'login_nav.html', return TemplateResponse(request, 'base.html',
{'title': _('XMPP Server'), {'title': _('XMPP Server'),
'main': main, 'main': main,
'sidebar_right': sidebar_right}) 'sidebar_right': sidebar_right})
@ -62,13 +63,12 @@ def configure(request):
status = get_status() status = get_status()
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = ConfigureForm(request.POST, prefix='xmpp') form = ConfigureForm(request.POST, prefix='xmpp')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_apply_changes(status, form.cleaned_data, messages) _apply_changes(request, status, form.cleaned_data)
status = get_status() status = get_status()
form = ConfigureForm(initial=status, prefix='xmpp') form = ConfigureForm(initial=status, prefix='xmpp')
else: else:
@ -80,7 +80,6 @@ def configure(request):
return TemplateResponse(request, 'xmpp_configure.html', return TemplateResponse(request, 'xmpp_configure.html',
{'title': _('Configure XMPP Server'), {'title': _('Configure XMPP Server'),
'form': form, 'form': form,
'messages_': messages,
'sidebar_right': sidebar_right}) 'sidebar_right': sidebar_right})
@ -93,19 +92,19 @@ def get_status():
return {'inband_enabled': 'inband_enable' in output.split()} return {'inband_enabled': 'inband_enable' in output.split()}
def _apply_changes(old_status, new_status, messages): def _apply_changes(request, old_status, new_status):
"""Apply the form changes""" """Apply the form changes"""
cfg.log.info('Status - %s, %s' % (old_status, new_status)) cfg.log.info('Status - %s, %s' % (old_status, new_status))
if old_status['inband_enabled'] == new_status['inband_enabled']: if old_status['inband_enabled'] == new_status['inband_enabled']:
messages.append(('info', _('Setting unchanged'))) messages.info(request, _('Setting unchanged'))
return return
if new_status['inband_enabled']: if new_status['inband_enabled']:
messages.append(('success', _('Inband registration enabled'))) messages.success(request, _('Inband registration enabled'))
option = 'inband_enable' option = 'inband_enable'
else: else:
messages.append(('success', _('Inband registration disabled'))) messages.success(request, _('Inband registration disabled'))
option = 'noinband_enable' option = 'noinband_enable'
cfg.log.info('Option - %s' % option) cfg.log.info('Option - %s' % option)
@ -128,13 +127,12 @@ class RegisterForm(forms.Form): # pylint: disable-msg=W0232
def register(request): def register(request):
"""Serve the registration form""" """Serve the registration form"""
form = None form = None
messages = []
if request.method == 'POST': if request.method == 'POST':
form = RegisterForm(request.POST, prefix='xmpp') form = RegisterForm(request.POST, prefix='xmpp')
# pylint: disable-msg=E1101 # pylint: disable-msg=E1101
if form.is_valid(): if form.is_valid():
_register_user(form.cleaned_data, messages) _register_user(request, form.cleaned_data)
form = RegisterForm(prefix='xmpp') form = RegisterForm(prefix='xmpp')
else: else:
form = RegisterForm(prefix='xmpp') form = RegisterForm(prefix='xmpp')
@ -145,11 +143,10 @@ def register(request):
return TemplateResponse(request, 'xmpp_register.html', return TemplateResponse(request, 'xmpp_register.html',
{'title': _('Register XMPP Account'), {'title': _('Register XMPP Account'),
'form': form, 'form': form,
'messages_': messages,
'sidebar_right': sidebar_right}) 'sidebar_right': sidebar_right})
def _register_user(data, messages): def _register_user(request, data):
"""Register a new XMPP user""" """Register a new XMPP user"""
output, error = actions.superuser_run( output, error = actions.superuser_run(
'xmpp-register', [data['username'], data['password']]) 'xmpp-register', [data['username'], data['password']])
@ -157,10 +154,9 @@ def _register_user(data, messages):
raise Exception('Error registering user - %s' % error) raise Exception('Error registering user - %s' % error)
if 'successfully registered' in output: if 'successfully registered' in output:
messages.append(('success', messages.success(request, _('Registered account for %s') %
_('Registered account for %s' % data['username'])
data['username'])))
else: else:
messages.append(('error', messages.error(request,
_('Failed to register account for %s: %s') % _('Failed to register account for %s: %s') %
(data['username'], output))) (data['username'], output))

104
plinth.py
View File

@ -1,21 +1,21 @@
#!/usr/bin/env python #!/usr/bin/env python
import os, sys, argparse import argparse
import cfg import os
import sys
import django.conf import django.conf
import django.core.wsgi import django.core.wsgi
if not os.path.join(cfg.file_root, "vendor") in sys.path:
sys.path.append(os.path.join(cfg.file_root, "vendor"))
import cherrypy import cherrypy
from cherrypy import _cpserver from cherrypy import _cpserver
from cherrypy.process.plugins import Daemonizer from cherrypy.process.plugins import Daemonizer
Daemonizer(cherrypy.engine).subscribe()
import cfg
import module_loader import module_loader
import plugin_mount import plugin_mount
import service import service
import logger
from logger import Logger from logger import Logger
__version__ = "0.2.14" __version__ = "0.2.14"
@ -28,64 +28,52 @@ __status__ = "Development"
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser(description='Plinth web interface for the FreedomBox.') """Parse command line arguments"""
parser.add_argument('--pidfile', parser = argparse.ArgumentParser(
help='specify a file in which the server may write its pid') description='Plinth web interface for FreedomBox')
# FIXME make this work with basehref for static files. parser.add_argument(
parser.add_argument('--server_dir', '--pidfile', default='plinth.pid',
help='specify where to host the server.') help='specify a file in which the server may write its pid')
parser.add_argument("--debug", action="store_true", parser.add_argument(
help="Debug flag. Don't use.") '--server_dir', default='/',
help='web server path under which to serve')
parser.add_argument(
'--debug', action='store_true', default=False,
help='enable debugging and run server *insecurely*')
parser.add_argument(
'--no-daemon', action='store_true', default=False,
help='do not start as a daemon')
args=parser.parse_args() args = parser.parse_args()
set_config(args, "pidfile", "plinth.pid") cfg.pidfile = args.pidfile
set_config(args, "server_dir", "/") cfg.server_dir = args.server_dir
set_config(args, "debug", False) cfg.debug = args.debug
cfg.no_daemon = args.no_daemon
return cfg
def set_config(args, element, default):
"""Sets *cfg* elements based on *args* values, or uses a reasonable default.
- If values are passed in from *args*, use those.
- If values are read from the config file, use those.
- If no values have been given, use default.
"""
try:
# cfg.(element) = args.(element)
setattr(cfg, element, getattr(args, element))
except AttributeError:
# if it fails, we didn't receive that argument.
try:
# if not cfg.(element): cfg.(element) = default
if not getattr(cfg, element):
setattr(cfg, element, default)
except AttributeError:
# it wasn't in the config file, but set the default anyway.
setattr(cfg, element, default)
def setup_logging(): def setup_logging():
"""Setup logging framework""" """Setup logging framework"""
cfg.log = Logger() cfg.log = Logger()
logger.init()
def setup_configuration(): def setup_paths():
cfg = parse_arguments() """Setup current directory and python import paths"""
os.chdir(cfg.python_root)
try: if not os.path.join(cfg.file_root, 'vendor') in sys.path:
if cfg.pidfile: sys.path.append(os.path.join(cfg.file_root, 'vendor'))
from cherrypy.process.plugins import PIDFile
PIDFile(cherrypy.engine, cfg.pidfile).subscribe()
except AttributeError:
pass
os.chdir(cfg.python_root)
def setup_server(): def setup_server():
"""Setup CherryPy server""" """Setup CherryPy server"""
# Set the PID file path
try:
if cfg.pidfile:
from cherrypy.process.plugins import PIDFile
PIDFile(cherrypy.engine, cfg.pidfile).subscribe()
except AttributeError:
pass
# Add an extra server # Add an extra server
server = _cpserver.Server() server = _cpserver.Server()
server.socket_host = '127.0.0.1' server.socket_host = '127.0.0.1'
@ -107,6 +95,9 @@ def setup_server():
'tools.staticdir.dir': '.'}} 'tools.staticdir.dir': '.'}}
cherrypy.tree.mount(None, cfg.server_dir + '/static', config) cherrypy.tree.mount(None, cfg.server_dir + '/static', config)
if not cfg.no_daemon:
Daemonizer(cherrypy.engine).subscribe()
cherrypy.engine.signal_handler.subscribe() cherrypy.engine.signal_handler.subscribe()
@ -141,10 +132,11 @@ def configure_django():
template_directories = module_loader.get_template_directories() template_directories = module_loader.get_template_directories()
sessions_directory = os.path.join(cfg.data_dir, 'sessions') sessions_directory = os.path.join(cfg.data_dir, 'sessions')
django.conf.settings.configure( django.conf.settings.configure(
DEBUG=False, DEBUG=cfg.debug,
ALLOWED_HOSTS=['127.0.0.1', 'localhost'], ALLOWED_HOSTS=['127.0.0.1', 'localhost'],
TEMPLATE_DIRS=template_directories, TEMPLATE_DIRS=template_directories,
INSTALLED_APPS=['bootstrapform'], INSTALLED_APPS=['bootstrapform',
'django.contrib.messages'],
ROOT_URLCONF='urls', ROOT_URLCONF='urls',
SESSION_ENGINE='django.contrib.sessions.backends.file', SESSION_ENGINE='django.contrib.sessions.backends.file',
SESSION_FILE_PATH=sessions_directory, SESSION_FILE_PATH=sessions_directory,
@ -154,11 +146,15 @@ def configure_django():
def main(): def main():
"""Intialize and start the application""" """Intialize and start the application"""
parse_arguments()
cfg.read()
setup_logging() setup_logging()
service.init() service.init()
setup_configuration() setup_paths()
configure_django() configure_django()

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,4 +1,4 @@
{% extends 'login_nav.html' %} {% extends 'base.html' %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -53,16 +53,50 @@
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <a class="btn btn-navbar" data-toggle="collapse"
data-target=".nav-collapse">
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</a> </a>
<a href="{{ basehref }}/" class="logo-top"> <a href="{{ basehref }}/" class="logo-top">
<img src="{% static 'theme/img/freedombox-logo-32px.png' %}" alt="FreedomBox" /> <img src="{% static 'theme/img/freedombox-logo-32px.png' %}"
alt="FreedomBox" />
</a> </a>
<a class="brand" href="{{ basehref }}/">FreedomBox</a> <a class="brand" href="{{ basehref }}/">FreedomBox</a>
{% block add_nav_and_login %} {% block add_nav_and_login %}
<div class="nav-collapse">
<ul class="nav">
{% for item in main_menu.items %}
{% if item.url in active_menu_urls %}
<li class="active">
<a href="{{ item.url }}" class="active">
{% else %}
<li>
<a href="{{ item.url }}">
{% endif %}
<span class="{{ item.icon }} icon-white nav-icon"></span>
{{ item.label }}
</a>
</li>
{% endfor %}
</ul>
{% if username %}
<p class="navbar-text pull-right">
<i class="icon-user icon-white nav-icon"></i>
Logged in as <a href="{{ username }}">{{ username }}</a>.
<a href="{{ basehref }}/auth/logout" title="Log out">
Log out</a>.
</p>
{% else %}
<p class="navbar-text pull-right">
Not logged in.
<i class="icon-user icon-white nav-icon"></i>
<a href="{{ basehref }}/auth/login" title="Log in">
Log in</a>.
</p>
{% endif %}
{% endblock %} {% endblock %}
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div> </div>
@ -99,6 +133,9 @@
{{ title }} {{ title }}
{% endblock %} {% endblock %}
</h2> </h2>
{% include 'messages.html' %}
{% block main_block %} {% block main_block %}
{{ main|safe }} {{ main|safe }}
{% endblock %} {% endblock %}

View File

@ -1,7 +0,0 @@
{% extends "login_nav.html" %}
{% block title_block %}
<span class="label label-important error-large">Error: {{ title }}</span>
<br />
<br />
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "login_nav.html" %} {% extends "base.html" %}
{% comment %} {% comment %}
# #
# This file is part of Plinth. # This file is part of Plinth.

View File

@ -1,27 +0,0 @@
{% extends "base.html" %}
{% block add_nav_and_login %}
<div class="nav-collapse">
<ul class="nav">
{% for item in main_menu.items %}
{% if item.url in active_menu_urls %}
<li class="active">
<a href="{{ item.url }}" class="active">
{% else %}
<li>
<a href="{{ item.url }}">
{% endif %}
<span class="{{ item.icon }} icon-white nav-icon"></span>
{{ item.label }}
</a>
</li>
{% endfor %}
</ul>
{% if username %}
<p class="navbar-text pull-right"><i class="icon-user icon-white nav-icon"></i>Logged in as <a href="{{ username }}">{{ username }}</a>. <a href="{{ basehref }}/auth/logout" title="Log out">Log out</a>.</p>
{% else %}
<p class="navbar-text pull-right">Not logged in. <i class="icon-user icon-white nav-icon"></i><a href="{{ basehref }}/auth/login" title="Log in">Log in</a>.</p>
{% endif %}
{% endblock %}

View File

@ -17,8 +17,8 @@
# #
{% endcomment %} {% endcomment %}
{% for severity, message in messages_ %} {% for message in messages %}
<div class='alert alert-{{ severity }} alert-dismissable'> <div class='alert alert-{{ message.tags }} alert-dismissable'>
<a class="close" data-dismiss="alert">&times;</a> <a class="close" data-dismiss="alert">&times;</a>
{{ message }} {{ message }}
</div> </div>

View File

@ -1,24 +0,0 @@
{{ extends "base.html" }}
{% block css %}
<!-- CSS from previous template, not sure what to keep yet -->
<!--<link rel="stylesheet" href="{{ basehref }}/static/theme/style-plinth-2col.css" />-->
{% endblock %}
{% block add_sidebar_left %}
<div class="span3">
<div class="well sidebar-nav">
{% block nav_block %}
{{ nav }}
{% endblock %}
{% block sidebar_left_block %}
{{ sidebar_left }}
{% endblock %}
</div><!--/.well -->
<div class="well sidebar-nav">
{% block sidebar_right_block %}
{{ sidebar_right }}
{% endblock %}
</div><!--/.well -->
</div><!--/span-->
{% endblock %}

22
util.py
View File

@ -24,35 +24,17 @@ def mkdir(newdir):
#print "mkdir %s" % repr(newdir) #print "mkdir %s" % repr(newdir)
if tail: if tail:
os.mkdir(newdir) os.mkdir(newdir)
def is_string(obj):
isinstance(obj, basestring)
def is_ascii(s):
return all(ord(c) < 128 for c in s)
def is_alphanumeric(string):
for c in string:
o = ord(c)
if not o in range(48, 58) + range(41, 91) + [95] + range(97, 123):
return False
return True
def slurp(filespec): def slurp(filespec):
with open(filespec) as x: f = x.read() with open(filespec) as x: f = x.read()
return f return f
def unslurp(filespec, msg): def unslurp(filespec, msg):
with open(filespec, 'w') as x: with open(filespec, 'w') as x:
x.write(msg) x.write(msg)
def find_in_seq(func, seq):
"Return first item in seq for which func(item) returns True."
for i in seq:
if func(i):
return i
def find_keys(dic, val):
"""return the key of dictionary dic given the value"""
return [k for k, v in dic.iteritems() if v == val]
def filedict_con(filespec=None, table='dict'): def filedict_con(filespec=None, table='dict'):
"""TODO: better error handling in filedict_con""" """TODO: better error handling in filedict_con"""