2013-09-08 14:30:26 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2013-10-13 17:17:56 +02:00
|
|
|
import ctypes
|
2013-09-08 14:30:26 +02:00
|
|
|
import os
|
2013-10-13 17:17:56 +02:00
|
|
|
import socket
|
2013-09-08 14:30:26 +02:00
|
|
|
import sys
|
2013-10-12 21:30:34 +02:00
|
|
|
import tempfile
|
2013-10-28 16:34:53 +01:00
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
import webbrowser
|
|
|
|
|
|
|
|
from base64 import b64encode
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
from django.conf import ENVIRONMENT_VARIABLE
|
2013-09-08 14:30:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
UNIX_VERSION = 'Unix Version'
|
|
|
|
WINDOWS_VERSION = 'Windows Version'
|
|
|
|
WINDOWS_PORTABLE_VERSION = 'Windows Portable Version'
|
|
|
|
|
2013-10-13 17:17:56 +02:00
|
|
|
|
2013-10-12 21:30:34 +02:00
|
|
|
class PortableDirNotWritable(Exception):
|
|
|
|
pass
|
2013-09-08 14:30:26 +02:00
|
|
|
|
2013-10-13 17:17:56 +02:00
|
|
|
|
2013-11-06 17:49:41 +01:00
|
|
|
class PortIsBlockedError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-10-28 16:34:53 +01:00
|
|
|
class DatabaseInSettingsError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-08 14:30:26 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2013-11-10 20:24:47 +01:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2013-09-08 14:30:26 +02:00
|
|
|
def detect_openslides_type():
|
|
|
|
"""
|
2013-10-13 17:17:56 +02:00
|
|
|
Returns the type of this OpenSlides version.
|
2013-09-08 14:30:26 +02:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2013-10-28 16:34:53 +01:00
|
|
|
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")
|
2013-11-10 20:24:47 +01:00
|
|
|
settings_module_dir = unicode2filesystem(os.path.dirname(settings_path)) # TODO: Use absolute path here or not?
|
2013-10-28 16:34:53 +01:00
|
|
|
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)
|
2013-11-10 20:24:47 +01:00
|
|
|
print('Settings file at %s successfully created.' % unicode2filesystem(settings))
|
2013-10-28 16:34:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_default_settings_context(user_data_path=None):
|
|
|
|
"""
|
|
|
|
Returns the default context values for the settings template.
|
|
|
|
|
|
|
|
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
|
|
|
|
if user_data_path:
|
|
|
|
default_context = get_user_data_path_values(
|
|
|
|
user_data_path=user_data_path,
|
|
|
|
default=False)
|
|
|
|
else:
|
|
|
|
openslides_type = detect_openslides_type()
|
|
|
|
user_data_path = get_default_user_data_path(openslides_type)
|
|
|
|
default_context = get_user_data_path_values(
|
|
|
|
user_data_path=user_data_path,
|
|
|
|
default=True,
|
|
|
|
openslides_type=openslides_type)
|
|
|
|
default_context['debug'] = 'False'
|
|
|
|
return default_context
|
|
|
|
|
|
|
|
|
2013-10-13 17:17:56 +02:00
|
|
|
def get_default_user_data_path(openslides_type):
|
2013-09-08 14:30:26 +02:00
|
|
|
"""
|
2013-10-13 17:17:56 +02:00
|
|
|
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.
|
2013-09-08 14:30:26 +02:00
|
|
|
"""
|
2013-10-13 17:17:56 +02:00
|
|
|
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
|
2013-09-08 14:30:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
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:
|
2013-10-13 17:17:56 +02:00
|
|
|
raise Exception("Could not determine Windows' APPDATA path")
|
2013-09-08 14:30:26 +02:00
|
|
|
|
|
|
|
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:
|
2013-10-12 21:30:34 +02:00
|
|
|
raise PortableDirNotWritable(
|
|
|
|
'Portable directory is not writeable. '
|
2013-10-13 17:17:56 +02:00
|
|
|
'Please choose another directory for settings and data files.')
|
2013-10-12 21:30:34 +02:00
|
|
|
else:
|
2013-09-08 14:30:26 +02:00
|
|
|
os.close(fd)
|
|
|
|
os.unlink(test_file)
|
|
|
|
return portable_path
|
|
|
|
|
|
|
|
|
2013-10-28 16:34:53 +01:00
|
|
|
def get_user_data_path_values(user_data_path, default=False, openslides_type=None):
|
|
|
|
"""
|
|
|
|
Returns a dictionary of the user specific data path values for the new
|
|
|
|
settings file.
|
|
|
|
|
|
|
|
The argument 'user_data_path' is a path to the directory where OpenSlides
|
|
|
|
should store the user specific data like SQLite3 database, media and search
|
|
|
|
index.
|
|
|
|
|
|
|
|
The argument 'default' is a simple flag. If it is True and the OpenSlides
|
|
|
|
type is the Windows portable version, the returned dictionary contains
|
|
|
|
strings of callable functions for the settings file, else it contains
|
|
|
|
string paths.
|
|
|
|
|
|
|
|
The argument 'openslides_type' can to be one of the three types mentioned in
|
|
|
|
openslides.utils.main.
|
|
|
|
"""
|
|
|
|
if default and openslides_type == WINDOWS_PORTABLE_VERSION:
|
|
|
|
user_data_path_values = {}
|
|
|
|
user_data_path_values['import_function'] = 'from openslides.utils.main import get_portable_paths'
|
|
|
|
user_data_path_values['database_path_value'] = "get_portable_paths('database')"
|
|
|
|
user_data_path_values['media_path_value'] = "get_portable_paths('media')"
|
|
|
|
user_data_path_values['whoosh_index_path_value'] = "get_portable_paths('whoosh_index')"
|
|
|
|
else:
|
|
|
|
user_data_path_values = get_user_data_path_values_with_path(user_data_path, 'openslides')
|
|
|
|
return user_data_path_values
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_data_path_values_with_path(*paths):
|
|
|
|
"""
|
|
|
|
Returns a dictionary of the user specific data path values for the new
|
|
|
|
settings file. Therefor it uses the given arguments as parts of the path.
|
|
|
|
"""
|
|
|
|
final_path = os.path.abspath(os.path.join(*paths))
|
|
|
|
user_data_path_values = {}
|
|
|
|
user_data_path_values['import_function'] = ''
|
|
|
|
variables = (('database_path_value', 'database.sqlite'),
|
|
|
|
('media_path_value', 'media'),
|
|
|
|
('whoosh_index_path_value', 'whoosh_index'))
|
|
|
|
for key, value in variables:
|
|
|
|
path_list = [final_path, value]
|
|
|
|
if '.' not in value:
|
|
|
|
path_list.append('')
|
|
|
|
user_data_path_values[key] = repr(
|
|
|
|
filesystem2unicode(os.path.join(*path_list)))
|
|
|
|
return user_data_path_values
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2013-09-08 14:30:26 +02:00
|
|
|
def get_portable_paths(name):
|
|
|
|
"""
|
|
|
|
Returns the paths for the Windows portable version on runtime for the
|
2013-10-13 17:17:56 +02:00
|
|
|
SQLite3 database, the media directory and the search index. The argument
|
|
|
|
'name' can be 'database', 'media' or 'whoosh_index'.
|
2013-09-08 14:30:26 +02:00
|
|
|
"""
|
|
|
|
if name == 'database':
|
|
|
|
path = os.path.join(get_win32_portable_path(), 'openslides', 'database.sqlite')
|
|
|
|
elif name == 'media':
|
|
|
|
path = os.path.join(get_win32_portable_path(), 'openslides', 'media', '')
|
2013-10-13 17:17:56 +02:00
|
|
|
elif name == 'whoosh_index':
|
|
|
|
path = os.path.join(get_win32_portable_path(), 'openslides', 'whoosh_index', '')
|
2013-09-08 14:30:26 +02:00
|
|
|
else:
|
2013-10-13 17:17:56 +02:00
|
|
|
raise TypeError('Unknown type %s' % name)
|
2013-09-08 14:30:26 +02:00
|
|
|
return path
|
2013-10-13 17:17:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
def get_port(address, port):
|
|
|
|
"""
|
2013-11-06 17:49:41 +01:00
|
|
|
Checks if the port for the server is available and returns it the port. If
|
|
|
|
it is port 80, try also port 8000.
|
2013-10-13 17:17:56 +02:00
|
|
|
|
|
|
|
The argument 'address' should be an IP address. The argument 'port' should
|
|
|
|
be an integer.
|
|
|
|
"""
|
2013-11-06 17:49:41 +01:00
|
|
|
s = socket.socket()
|
|
|
|
try:
|
2013-11-12 21:39:03 +01:00
|
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
2013-11-06 17:49:41 +01:00
|
|
|
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)
|
2013-10-13 17:17:56 +02:00
|
|
|
return port
|
2013-10-28 16:34:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
browser = webbrowser.get()
|
|
|
|
|
|
|
|
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:
|
2014-01-11 08:50:52 +01:00
|
|
|
raise DatabaseInSettingsError('No path or name specified for default database.')
|
2013-10-28 16:34:53 +01:00
|
|
|
if default.get('ENGINE') != 'django.db.backends.sqlite3':
|
|
|
|
database_path = None
|
|
|
|
return database_path
|