OpenSlides/openslides/utils/main.py

330 lines
11 KiB
Python

# -*- coding: utf-8 -*-
import ctypes
import os
import socket
import sys
import tempfile
import threading
import time
import webbrowser
from base64 import b64encode
from django.core.exceptions import ImproperlyConfigured
from django.conf import ENVIRONMENT_VARIABLE
from django.utils.translation import activate, check_for_language, get_language
from django.utils.translation import ugettext as _
UNIX_VERSION = 'Unix Version'
WINDOWS_VERSION = 'Windows Version'
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
class PortableDirNotWritable(Exception):
pass
class PortIsBlockedError(Exception):
pass
class DatabaseInSettingsError(Exception):
pass
def filesystem2unicode(path):
"""
Transforms a path string to unicode according to the filesystem's encoding.
"""
# TODO: Delete this function after switch to Python 3.
if not isinstance(path, unicode):
filesystem_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
path = path.decode(filesystem_encoding)
return path
def unicode2filesystem(path):
"""
Transforms a path unicode to string according to the filesystem's encoding.
"""
# TODO: Delete this function after switch to Python 3.
if not isinstance(path, str):
filesystem_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
path = path.encode(filesystem_encoding)
return path
def detect_openslides_type():
"""
Returns the type of this OpenSlides version.
"""
if sys.platform == 'win32':
if os.path.basename(sys.executable).lower() == 'openslides.exe':
# Note: sys.executable is the path of the *interpreter*
# the portable version embeds python so it *is* the interpreter.
# The wrappers generated by pip and co. will spawn the usual
# python(w).exe, so there is no danger of mistaking them
# for the portable even though they may also be called
# openslides.exe
openslides_type = WINDOWS_PORTABLE_VERSION
else:
openslides_type = WINDOWS_VERSION
else:
openslides_type = UNIX_VERSION
return openslides_type
def get_default_settings_path(openslides_type):
"""
Returns the default settings path according to the OpenSlides type.
The argument 'openslides_type' has to be one of the three types mentioned in
openslides.utils.main.
"""
if openslides_type == UNIX_VERSION:
parent_directory = filesystem2unicode(os.environ.get(
'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config')))
elif openslides_type == WINDOWS_VERSION:
parent_directory = get_win32_app_data_path()
elif openslides_type == WINDOWS_PORTABLE_VERSION:
parent_directory = get_win32_portable_path()
else:
raise TypeError('%s is not a valid OpenSlides type.' % openslides_type)
return os.path.join(parent_directory, 'openslides', 'settings.py')
def setup_django_settings_module(settings_path):
"""
Sets the environment variable ENVIRONMENT_VARIABLE, that means
'DJANGO_SETTINGS_MODULE', to the given settings.
"""
settings_file = os.path.basename(settings_path)
settings_module_name = ".".join(settings_file.split('.')[:-1])
if '.' in settings_module_name:
raise ImproperlyConfigured("'.' is not an allowed character in the settings-file")
settings_module_dir = unicode2filesystem(os.path.dirname(settings_path)) # TODO: Use absolute path here or not?
sys.path.insert(0, settings_module_dir)
os.environ[ENVIRONMENT_VARIABLE] = '%s' % settings_module_name
def ensure_settings(settings, args):
"""
Create settings if a settings path is given and this file still does not exist.
"""
if settings and not os.path.exists(settings):
if not hasattr(args, 'user_data_path'):
context = get_default_settings_context()
else:
context = get_default_settings_context(args.user_data_path)
write_settings(settings, **context)
print('Settings file at %s successfully created.' % unicode2filesystem(settings))
def get_default_settings_context(user_data_path=None):
"""
Returns the default context values for the settings template:
'openslides_user_data_path', 'import_function' and 'debug'.
The argument 'user_data_path' is a given path for user specific data or None.
"""
# Setup path for user specific data (SQLite3 database, media, search index, ...):
# Take it either from command line or get default path
default_context = {}
if user_data_path:
default_context['openslides_user_data_path'] = repr(user_data_path)
default_context['import_function'] = ''
else:
openslides_type = detect_openslides_type()
if openslides_type == WINDOWS_PORTABLE_VERSION:
default_context['openslides_user_data_path'] = 'get_win32_portable_user_data_path()'
default_context['import_function'] = 'from openslides.utils.main import get_win32_portable_user_data_path'
else:
path = get_default_user_data_path(openslides_type)
default_context['openslides_user_data_path'] = repr(os.path.join(path, 'openslides'))
default_context['import_function'] = ''
default_context['debug'] = 'False'
return default_context
def get_default_user_data_path(openslides_type):
"""
Returns the default path for user specific data according to the OpenSlides
type.
The argument 'openslides_type' has to be one of the three types mentioned
in openslides.utils.main.
"""
if openslides_type == UNIX_VERSION:
default_user_data_path = filesystem2unicode(os.environ.get(
'XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share')))
elif openslides_type == WINDOWS_VERSION:
default_user_data_path = get_win32_app_data_path()
elif openslides_type == WINDOWS_PORTABLE_VERSION:
default_user_data_path = get_win32_portable_path()
else:
raise TypeError('%s is not a valid OpenSlides type.' % openslides_type)
return default_user_data_path
def get_win32_app_data_path():
"""
Returns the path to Windows' AppData directory.
"""
shell32 = ctypes.WinDLL("shell32.dll")
SHGetFolderPath = shell32.SHGetFolderPathW
SHGetFolderPath.argtypes = (
ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32,
ctypes.c_wchar_p)
SHGetFolderPath.restype = ctypes.c_uint32
CSIDL_LOCAL_APPDATA = 0x001c
MAX_PATH = 260
buf = ctypes.create_unicode_buffer(MAX_PATH)
res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
if res != 0:
raise Exception("Could not determine Windows' APPDATA path")
return buf.value
def get_win32_portable_path():
"""
Returns the path to the Windows portable version.
"""
# NOTE: sys.executable will be the path to openslides.exe
# since it is essentially a small wrapper that embeds the
# python interpreter
portable_path = filesystem2unicode(os.path.dirname(os.path.abspath(sys.executable)))
try:
fd, test_file = tempfile.mkstemp(dir=portable_path)
except OSError:
raise PortableDirNotWritable(
'Portable directory is not writeable. '
'Please choose another directory for settings and data files.')
else:
os.close(fd)
os.unlink(test_file)
return portable_path
def get_win32_portable_user_data_path():
"""
Returns the user data path to the Windows portable version.
"""
return os.path.join(get_win32_portable_path(), 'openslides')
def write_settings(settings_path, template=None, **context):
"""
Creates the settings file at the given path using the given values for the
file template.
"""
if template is None:
with open(os.path.join(os.path.dirname(__file__), 'settings.py.tpl')) as template_file:
template = template_file.read()
context.setdefault('secret_key', b64encode(os.urandom(30)))
content = template % context
settings_module = os.path.realpath(os.path.dirname(settings_path))
if not os.path.exists(settings_module):
os.makedirs(settings_module)
with open(settings_path, 'w') as settings_file:
settings_file.write(content)
def get_port(address, port):
"""
Checks if the port for the server is available and returns it the port. If
it is port 80, try also port 8000.
The argument 'address' should be an IP address. The argument 'port' should
be an integer.
"""
s = socket.socket()
try:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((address, port))
s.listen(-1)
except socket.error:
error = True
else:
error = False
finally:
s.close()
if error:
if port == 80:
port = get_port(address, 8000)
else:
raise PortIsBlockedError('Port %d is not available. Try another port using the --port option.' % port)
return port
def get_browser_url(address, port):
"""
Returns the url to open the web browser.
The argument 'address' should be an IP address. The argument 'port' should
be an integer.
"""
browser_url = 'http://'
if address == '0.0.0.0':
browser_url += 'localhost'
else:
browser_url += address
if not port == 80:
browser_url += ":%d" % port
return browser_url
def start_browser(browser_url):
"""
Launches the default web browser at the given url and opens the
webinterface.
"""
try:
browser = webbrowser.get()
except webbrowser.Error as e:
print('Web browser controller error: %s' % e)
else:
def function():
# TODO: Use a nonblocking sleep event here. Tornado has such features.
time.sleep(1)
browser.open(browser_url)
thread = threading.Thread(target=function)
thread.start()
def get_database_path_from_settings():
"""
Retrieves the database path out of the settings file. Returns None,
if it is not a SQLite3 database.
"""
from django.conf import settings as django_settings
from django.db import DEFAULT_DB_ALIAS
db_settings = django_settings.DATABASES
default = db_settings.get(DEFAULT_DB_ALIAS)
if not default:
raise DatabaseInSettingsError("Default databases is not configured")
database_path = default.get('NAME')
if not database_path:
raise DatabaseInSettingsError('No path or name specified for default database.')
if default.get('ENGINE') != 'django.db.backends.sqlite3':
database_path = None
return database_path
def translate_customizable_strings(language_code):
"""
Translates all translatable config values and saves them into database.
"""
if check_for_language(language_code):
from openslides.config.api import config
current_language = get_language()
activate(language_code)
for name in config.get_all_translatable():
config[name] = _(config[name])
activate(current_language)