diff --git a/data/etc/plinth/plinth.config b/data/etc/plinth/plinth.config index cf89d43c9..297f22730 100644 --- a/data/etc/plinth/plinth.config +++ b/data/etc/plinth/plinth.config @@ -13,7 +13,7 @@ actions_dir = /usr/share/plinth/actions doc_dir = /usr/share/doc/plinth # file locations -store_file = %(data_dir)s/store.sqlite3 +store_file = %(data_dir)s/plinth.sqlite3 status_log_file = %(log_dir)s/status.log access_log_file = %(log_dir)s/access.log pidfile = %(pid_dir)s/plinth.pid diff --git a/plinth.config b/plinth.config index 8aeb93ba6..76979e838 100644 --- a/plinth.config +++ b/plinth.config @@ -13,7 +13,7 @@ actions_dir = %(file_root)s/actions doc_dir = %(file_root)s/doc # file locations -store_file = %(data_dir)s/store.sqlite3 +store_file = %(data_dir)s/plinth.sqlite3 status_log_file = %(log_dir)s/status.log access_log_file = %(log_dir)s/access.log pidfile = %(pid_dir)s/plinth.pid diff --git a/plinth/__main__.py b/plinth/__main__.py index dcfc6cf0d..0afabe998 100644 --- a/plinth/__main__.py +++ b/plinth/__main__.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python import argparse import django.conf @@ -160,8 +160,6 @@ def configure_django(): } } - data_file = os.path.join(cfg.data_dir, 'plinth.sqlite3') - template_directories = module_loader.get_template_directories() sessions_directory = os.path.join(cfg.data_dir, 'sessions') django.conf.settings.configure( @@ -170,12 +168,13 @@ def configure_django(): {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}, DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', - 'NAME': data_file}}, + 'NAME': cfg.store_file}}, DEBUG=cfg.debug, INSTALLED_APPS=['bootstrapform', 'django.contrib.auth', 'django.contrib.contenttypes', - 'django.contrib.messages'], + 'django.contrib.messages', + 'plinth'], LOGGING=logging_configuration, LOGIN_URL='lib:login', LOGIN_REDIRECT_URL='apps:index', @@ -199,10 +198,9 @@ def configure_django(): LOGGER.info('Configured Django') LOGGER.info('Template directories - %s', template_directories) - if not os.path.isfile(data_file): - LOGGER.info('Creating and initializing data file') - django.core.management.call_command('syncdb', interactive=False) - os.chmod(data_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) + LOGGER.info('Creating or adding new tables to data file') + django.core.management.call_command('syncdb', interactive=False) + os.chmod(cfg.store_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP) def main(): diff --git a/plinth/cfg.py b/plinth/cfg.py index 12a620fae..c816f52ac 100644 --- a/plinth/cfg.py +++ b/plinth/cfg.py @@ -70,7 +70,3 @@ def read(): 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] diff --git a/plinth/kvstore.py b/plinth/kvstore.py new file mode 100644 index 000000000..864b288ce --- /dev/null +++ b/plinth/kvstore.py @@ -0,0 +1,42 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Simple key/value store using Django models +""" + +from plinth.models import KVStore + + +def get(key): + """Return the value of a key""" + # pylint: disable-msg=E1101 + return KVStore.objects.get(pk=key).value + + +def get_default(key, default_value): + """Return the value of the key if key exists else return default_value""" + try: + return get(key) + except Exception: + return default_value + + +def set(key, value): # pylint: disable-msg=W0622 + """Store the value of a key""" + store = KVStore(key=key, value=value) + store.save() diff --git a/plinth/models.py b/plinth/models.py new file mode 100644 index 000000000..8dc1cfd03 --- /dev/null +++ b/plinth/models.py @@ -0,0 +1,39 @@ +# +# This file is part of Plinth. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +""" +Django models for the main application +""" + +from django.db import models +import json + + +class KVStore(models.Model): + """Model to store retrieve key/value configuration""" + key = models.TextField(primary_key=True) + value_json = models.TextField() + + @property + def value(self): + """Return the JSON decoded value of the key/value pair""" + return json.loads(self.value_json) + + @value.setter + def value(self, val): + """Store the value of the key/value pair by JSON encoding it""" + self.value_json = json.dumps(val) diff --git a/plinth/modules/first_boot/first_boot.py b/plinth/modules/first_boot/first_boot.py index 748e7ff4b..d997e71a9 100644 --- a/plinth/modules/first_boot/first_boot.py +++ b/plinth/modules/first_boot/first_boot.py @@ -26,10 +26,9 @@ from django.http.response import HttpResponseRedirect from django.template.response import TemplateResponse from gettext import gettext as _ -from plinth import cfg from plinth.modules.config import config from plinth.modules.lib.auth import add_user -from withsqlite.withsqlite import sqlite_db +from plinth import kvstore ## TODO: flesh out these tests values @@ -94,7 +93,7 @@ def state0(request): user. It's a good place to put error messages. """ try: - if _read_state() >= 5: + if kvstore.get_default('firstboot_state', 0) >= 5: return HttpResponseRedirect(reverse('index')) except KeyError: pass @@ -112,7 +111,7 @@ def state0(request): if success: # Everything is good, permanently mark and move to page 2 - _write_state(1) + kvstore.set('firstboot_state', 1) return HttpResponseRedirect(reverse('first_boot:state1')) else: form = State0Form(initial=status, prefix='firstboot') @@ -124,35 +123,29 @@ def state0(request): def get_state0(): """Return the state for form state 0""" - with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \ - database: - return {'hostname': config.get_hostname(), - 'box_key': database.get('box_key', None)} + return {'hostname': config.get_hostname(), + 'box_key': kvstore.get_default('box_key', None)} def _apply_state0(request, old_state, new_state): """Apply changes in state 0 form""" success = True - with sqlite_db(cfg.store_file, table="thisbox", autocommit=True) as \ - database: - database['about'] = 'Information about this FreedomBox' - if new_state['box_key']: - database['box_key'] = new_state['box_key'] - elif not old_state['box_key']: - database['box_key'] = generate_box_key() + if new_state['box_key']: + kvstore.set('box_key', new_state['box_key']) + elif not old_state['box_key']: + kvstore.set('box_key', generate_box_key()) - if old_state['hostname'] != new_state['hostname']: - config.set_hostname(new_state['hostname']) + if old_state['hostname'] != new_state['hostname']: + config.set_hostname(new_state['hostname']) - error = add_user(new_state['username'], new_state['password'], - 'First user, please change', '', True) - if error: - messages.error( - request, _('User account creation failed: %s') % error) - success = False - else: - messages.success(request, _('User account created')) + error = add_user(new_state['username'], new_state['password'], + 'First user, please change', '', True) + if error: + messages.error(request, _('User account creation failed: %s') % error) + success = False + else: + messages.success(request, _('User account created')) return success @@ -168,21 +161,7 @@ def state1(request): """ # TODO complete first_boot handling # Make sure the user is not stuck on a dead end for now. - _write_state(5) + kvstore.set('firstboot_state', 5) return TemplateResponse(request, 'firstboot_state1.html', {'title': _('Installing the Certificate')}) - - -def _read_state(): - """Read the current state from database""" - with sqlite_db(cfg.store_file, table='firstboot', - autocommit=True) as database: - return database['state'] - - -def _write_state(state): - """Write state to database""" - with sqlite_db(cfg.store_file, table='firstboot', - autocommit=True) as database: - database['state'] = state diff --git a/plinth/modules/first_boot/middleware.py b/plinth/modules/first_boot/middleware.py index ee7d7f5db..061230907 100644 --- a/plinth/modules/first_boot/middleware.py +++ b/plinth/modules/first_boot/middleware.py @@ -24,8 +24,7 @@ from django.core.urlresolvers import reverse from django.http.response import HttpResponseRedirect import logging -from plinth import cfg -from withsqlite.withsqlite import sqlite_db +from plinth import kvstore LOGGER = logging.getLogger(__name__) @@ -42,14 +41,13 @@ class FirstBootMiddleware(object): if request.path.startswith(reverse('first_boot:index')): return - with sqlite_db(cfg.store_file, table='firstboot') as database: - if 'state' not in database: - # Permanent redirect causes the browser to cache the redirect, - # preventing the user from navigating to /plinth until the - # browser is restarted. - return HttpResponseRedirect(reverse('first_boot:index')) + state = kvstore.get_default('firstboot_state', 0) + if not state: + # Permanent redirect causes the browser to cache the redirect, + # preventing the user from navigating to /plinth until the + # browser is restarted. + return HttpResponseRedirect(reverse('first_boot:index')) - if database['state'] < 5: - LOGGER.info('First boot state - %d', database['state']) - return HttpResponseRedirect(reverse('first_boot:state%d' % - database['state'])) + if state < 5: + LOGGER.info('First boot state - %d', state) + return HttpResponseRedirect(reverse('first_boot:state%d' % state))