# -*- coding: utf-8 -*-
#
#
# TheVirtualBrain-Framework Package. This package holds all Data Management, and
# Web-UI helpful to run brain-simulations. To use it, you also need to download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also http://www.thevirtualbrain.org
#
# (c) 2012-2023, Baycrest Centre for Geriatric Care ("Baycrest") and others
#
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU 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 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/>.
#
#
# CITATION:
# When using The Virtual Brain for scientific publications, please cite it as explained here:
# https://www.thevirtualbrain.org/tvb/zwei/neuroscience-publications
#
#
"""
.. moduleauthor:: Bogdan Neacsa <bogdan.neacsa@codemart.ro>
"""
import os
import subprocess
import threading
import cherrypy
import formencode
from time import sleep
from formencode import validators
from tvb.basic.profile import TvbProfile
from tvb.core.services.exceptions import InvalidSettingsException
from tvb.core.services.settings_service import SettingsService
from tvb.interfaces.web.controllers import common
from tvb.interfaces.web.controllers.autologging import traced
from tvb.interfaces.web.controllers.decorators import check_admin, using_template, jsonify, handle_error
from tvb.interfaces.web.controllers.users_controller import UserController
[docs]@traced
class SettingsController(UserController):
"""
Controller for TVB-Settings web page.
Inherit from UserController, to have the same fill_default_attributes method (with versionInfo).
"""
def __init__(self):
UserController.__init__(self)
self.settingsservice = SettingsService()
[docs] @cherrypy.expose
@handle_error(redirect=True)
@using_template('user/base_user')
@check_admin
def settings(self, save_settings=False, **data):
"""Main settings page submit and get"""
template_specification = dict(mainContent="settings/system_settings", title="System Settings")
if save_settings:
try:
form = SettingsForm()
data = form.to_python(data)
isrestart, isreset = self.settingsservice.save_settings(**data)
if isrestart:
thread = threading.Thread(target=self._restart_services, kwargs={'should_reset': isreset})
thread.start()
common.add2session(common.KEY_IS_RESTART, True)
common.set_important_message('Please wait until TVB is restarted properly!')
self.redirect('/tvb')
# Here we will leave the same settings page to be displayed.
# It will continue reloading when CherryPy restarts.
except formencode.Invalid as excep:
template_specification[common.KEY_ERRORS] = excep.unpack_errors()
except InvalidSettingsException as excep:
self.logger.error('Invalid settings! Exception %s was raised' % (str(excep)))
common.set_error_message(excep.message)
template_specification.update({'keys_order': self.settingsservice.KEYS_DISPLAY_ORDER,
'config_data': self.settingsservice.configurable_keys,
common.KEY_FIRST_RUN: TvbProfile.is_first_run()})
return self.fill_default_attributes(template_specification)
def _restart_services(self, should_reset):
"""
Restart CherryPy Backend.
"""
sleep(1)
cherrypy.engine.exit()
self.logger.info("Waiting for CherryPy to shut down ... ")
sleep(5)
python_path = TvbProfile.current.PYTHON_INTERPRETER_PATH
try:
import tvb_bin
proc_params = [python_path, '-m', 'tvb_bin.app', 'start', TvbProfile.CURRENT_PROFILE_NAME]
if should_reset:
proc_params.append('-reset')
subprocess.Popen(proc_params, shell=False)
except ImportError:
proc_params = [python_path, '-m', 'tvb.interfaces.web.run', TvbProfile.CURRENT_PROFILE_NAME, "tvb.config"]
if should_reset:
proc_params.append('reset')
subprocess.Popen(proc_params, shell=False).communicate()
self.logger.info("Starting CherryPy again ... ")
[docs] @cherrypy.expose
@handle_error(redirect=False)
@jsonify
def check_db_url(self, **data):
"""
Action on DB-URL validate button.
"""
try:
storage_path = data[self.settingsservice.KEY_STORAGE]
if os.path.isfile(storage_path):
raise InvalidSettingsException('TVB Storage should be set to a folder and not a file.')
if not os.path.isdir(storage_path):
try:
os.mkdir(storage_path)
except OSError:
return {'status': 'not ok',
'message': 'Could not create root storage for TVB. Please check write permissions!'}
self.settingsservice.check_db_url(data[self.settingsservice.KEY_DB_URL])
return {'status': 'ok', 'message': 'The database URL is valid.'}
except InvalidSettingsException as excep:
self.logger.error(excep)
return {'status': 'not ok', 'message': 'The database URL is not valid.'}
[docs]class DiskSpaceValidator(formencode.FancyValidator):
"""
Custom validator for TVB disk space / user.
"""
def _convert_to_python(self, value, _):
"""
Validation required method.
:param value is user-specified value, in MB
"""
try:
value = int(value)
return value
except ValueError:
raise formencode.Invalid('Invalid disk space %s. Should be number' % value, value, None)
[docs]class PortValidator(formencode.FancyValidator):
"""
Custom validator for OS Port number.
"""
def _convert_to_python(self, value, _):
"""
Validation required method.
"""
try:
value = int(value)
except ValueError:
raise formencode.Invalid('Invalid port %s. Should be number between 0 and 65535.' % value, value, None)
if 0 < value < 65535:
return value
else:
raise formencode.Invalid('Invalid port number %s. Should be in interval [0, 65535]' % value, value, None)
[docs]class ThreadNrValidator(formencode.FancyValidator):
"""
Custom validator number of threads.
"""
def _convert_to_python(self, value, _):
"""
Validation required method.
"""
try:
value = int(value)
except ValueError:
raise formencode.Invalid('Invalid number %s. Should be number between 1 and 16.' % value, value, None)
if 0 < value < 17:
return value
else:
raise formencode.Invalid('Invalid number %d. Should be in interval [1, 16]' % value, value, None)
[docs]class SurfaceVerticesNrValidator(formencode.FancyValidator):
"""
Custom validator for the number of vertices allowed for a surface
"""
# This limitation is given by our Max number of colors in pick mechanism
MAX_VALUE = 256 * 256 * 256 + 1
def _convert_to_python(self, value, _):
"""
Validation required method.
"""
msg = 'Invalid value: %s. Should be a number between 1 and %d.'
try:
value = int(value)
if 0 < value < self.MAX_VALUE:
return value
else:
raise formencode.Invalid(msg % (str(value), self.MAX_VALUE), value, None)
except ValueError:
raise formencode.Invalid(msg % (value, self.MAX_VALUE), value, None)
[docs]class AsciiValidator(formencode.FancyValidator):
"""
Allow only ascii strings
"""
def _convert_to_python(self, value, _):
try:
return str(value).encode('ascii')
except UnicodeError:
raise formencode.Invalid('Invalid ascii string %s' % value, '', None)