From 7bddb9635182c4c44da92d0ce00841ff4a2e84c1 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 8 May 2023 13:53:01 -0700 Subject: [PATCH] Add a custom WSGI application This change adds special WSGI entry points to replace the ones used by default in Kolibri. As a result, we can remove some complexity related to monkey-patching DynamicWhitenoise. --- Makefile | 7 --- src/kolibri_android/android_whitenoise.py | 11 ++-- src/kolibri_android/kolibri_extra/alt_wsgi.py | 39 ++++++++++++ src/kolibri_android/kolibri_extra/settings.py | 2 + src/kolibri_android/kolibri_extra/wsgi.py | 63 +++++++++++++++++++ src/kolibri_android/kolibri_utils.py | 9 --- .../main_activity/kolibri_bus.py | 20 +++++- 7 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 src/kolibri_android/kolibri_extra/alt_wsgi.py create mode 100644 src/kolibri_android/kolibri_extra/wsgi.py diff --git a/Makefile b/Makefile index 19b6d761..33561004 100644 --- a/Makefile +++ b/Makefile @@ -111,12 +111,6 @@ src/kolibri: clean patch -d src/ -p1 < patches/0001-Add-track-progress-information-to-channelimport.patch patch -d src/ -p1 < patches/0001-Break-up-DynamicWhiteNoise-code.patch -src/evil_kolibri: src/kolibri - mkdir -p src/evil_kolibri/utils - touch src/evil_kolibri/__init__.py - touch src/evil_kolibri/utils/__init__.py - cp src/kolibri/utils/kolibri_whitenoise.py src/evil_kolibri/utils/kolibri_whitenoise.py - .PHONY: apps-bundle.zip apps-bundle.zip: wget -N https://github.com/endlessm/kolibri-explore-plugin/releases/latest/download/apps-bundle.zip @@ -164,7 +158,6 @@ dist/version.json: needs-version DIST_DEPS = \ p4a_android_distro \ src/kolibri \ - src/evil_kolibri \ src/apps-bundle \ src/collections \ assets/welcomeScreen \ diff --git a/src/kolibri_android/android_whitenoise.py b/src/kolibri_android/android_whitenoise.py index 3471e7f8..812fb9d4 100644 --- a/src/kolibri_android/android_whitenoise.py +++ b/src/kolibri_android/android_whitenoise.py @@ -3,11 +3,11 @@ from urllib.parse import urlparse from django.utils.functional import cached_property -from evil_kolibri.utils.kolibri_whitenoise import compressed_file_extensions -from evil_kolibri.utils.kolibri_whitenoise import DynamicWhiteNoise -from evil_kolibri.utils.kolibri_whitenoise import EndRangeStaticFile -from evil_kolibri.utils.kolibri_whitenoise import FileFinder from jnius import autoclass +from kolibri.utils.kolibri_whitenoise import compressed_file_extensions +from kolibri.utils.kolibri_whitenoise import DynamicWhiteNoise +from kolibri.utils.kolibri_whitenoise import EndRangeStaticFile +from kolibri.utils.kolibri_whitenoise import FileFinder from whitenoise.httpstatus_backport import HTTPStatus from whitenoise.responders import NOT_ALLOWED_RESPONSE from whitenoise.responders import Response @@ -18,9 +18,6 @@ from .android_utils import open_file from .android_utils import stat_file -# We import from evil_kolibri so we can monkey-patch DynamicWhiteNoise from -# kolibri without creating a circular dependency. - logger = logging.getLogger(__name__) Uri = autoclass("android.net.Uri") diff --git a/src/kolibri_android/kolibri_extra/alt_wsgi.py b/src/kolibri_android/kolibri_extra/alt_wsgi.py new file mode 100644 index 00000000..24b13b03 --- /dev/null +++ b/src/kolibri_android/kolibri_extra/alt_wsgi.py @@ -0,0 +1,39 @@ +""" +WSGI config for the alternate origin server used for serving +sandboxed content +""" +import os + +import kolibri.core.content +from kolibri.core.content.utils import paths +from kolibri.core.content.zip_wsgi import get_application +from kolibri_android.android_whitenoise import AndroidDynamicWhiteNoise + +os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "kolibri.deployment.default.settings.base" +) + + +def generate_alt_wsgi_application(): + alt_content_path = "/" + paths.get_content_url( + paths.zip_content_path_prefix() + ).lstrip("/") + + content_dirs = [paths.get_content_dir_path()] + paths.get_content_fallback_paths() + + content_static_path = os.path.join( + os.path.dirname(kolibri.core.content.__file__), "static" + ) + + # Mount static files + return AndroidDynamicWhiteNoise( + get_application(), + dynamic_locations=[ + (alt_content_path, content_dir) for content_dir in content_dirs + ] + + [(paths.zip_content_static_root(), content_static_path)], + app_paths=[paths.get_zip_content_base_path()], + ) + + +alt_application = generate_alt_wsgi_application() diff --git a/src/kolibri_android/kolibri_extra/settings.py b/src/kolibri_android/kolibri_extra/settings.py index 593f56af..67994b62 100644 --- a/src/kolibri_android/kolibri_extra/settings.py +++ b/src/kolibri_android/kolibri_extra/settings.py @@ -7,6 +7,8 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False SESSION_COOKIE_AGE = 52560000 +WSGI_APPLICATION = "kolibri_android.kolibri_extra.wsgi.application" + MIDDLEWARE = list(MIDDLEWARE) + [ # noqa F405 "kolibri_android.kolibri_extra.middleware.AlwaysAuthenticatedMiddleware" diff --git a/src/kolibri_android/kolibri_extra/wsgi.py b/src/kolibri_android/kolibri_extra/wsgi.py new file mode 100644 index 00000000..ae218566 --- /dev/null +++ b/src/kolibri_android/kolibri_extra/wsgi.py @@ -0,0 +1,63 @@ +""" +WSGI config for Kolibri on Android. + +This is copied from kolibri/deployment/default/wsgi.py in upstream Kolibri, +but with a small change to use our Android variant of DynamicWhiteNoise. +""" +import os +import time + +from django.conf import settings +from django.core.wsgi import get_wsgi_application +from django.db.utils import OperationalError +from kolibri.core.content.utils import paths +from kolibri.utils import conf +from kolibri_android.android_whitenoise import AndroidDynamicWhiteNoise + +os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", "kolibri.deployment.default.settings.base" +) + + +def generate_wsgi_application(): + django_application = get_wsgi_application() + base_content_path = "/" + paths.get_content_url( + conf.OPTIONS["Deployment"]["URL_PATH_PREFIX"] + ).lstrip("/") + content_dirs = [paths.get_content_dir_path()] + paths.get_content_fallback_paths() + + # Mount static files + return AndroidDynamicWhiteNoise( + django_application, + static_prefix=settings.STATIC_URL, + dynamic_locations=[ + (base_content_path, content_dir) for content_dir in content_dirs + ] + + [(settings.MEDIA_URL, settings.MEDIA_ROOT)], + ) + + +application = None +tries_remaining = 6 +interval = 10 +while not application and tries_remaining: + try: + application = generate_wsgi_application() + except OperationalError: + # An OperationalError happens when sqlite vacuum is being + # executed. the db is locked + print( + "Database assumed to be undergoing a VACUUM, retrying again in {} seconds...".format( + interval + ) + ) + tries_remaining -= 1 + time.sleep(interval) + +if not application: + print( + "Could not start Kolibri with {} retries. Trying one last time".format( + tries_remaining + ) + ) + application = generate_wsgi_application() diff --git a/src/kolibri_android/kolibri_utils.py b/src/kolibri_android/kolibri_utils.py index f5960fbd..8736d70c 100644 --- a/src/kolibri_android/kolibri_utils.py +++ b/src/kolibri_android/kolibri_utils.py @@ -42,8 +42,6 @@ def init_kolibri(**kwargs): _update_kolibri_content_fallback_dirs() _update_explore_plugin_options() - _monkeypatch_whitenoise() - for plugin_name in DISABLED_PLUGINS: _kolibri_disable_plugin(plugin_name) @@ -122,13 +120,6 @@ def _update_kolibri_content_fallback_dirs(): os.environ["KOLIBRI_CONTENT_FALLBACK_DIRS"] = content_fallback_dirs -def _monkeypatch_whitenoise(): - from kolibri.utils import kolibri_whitenoise - - logger.info("Applying DynamicWhiteNoise workarounds") - kolibri_whitenoise.DynamicWhiteNoise = AndroidDynamicWhiteNoise - - def _kolibri_initialize(**kwargs): from kolibri.utils.main import initialize diff --git a/src/kolibri_android/main_activity/kolibri_bus.py b/src/kolibri_android/main_activity/kolibri_bus.py index 671cc4bb..323edd6e 100644 --- a/src/kolibri_android/main_activity/kolibri_bus.py +++ b/src/kolibri_android/main_activity/kolibri_bus.py @@ -21,9 +21,9 @@ def __init__(self, *args, enable_zeroconf=True, **kwargs): if enable_zeroconf: ZeroConfPlugin(self, self.port).subscribe() - KolibriServerPlugin(self, self.port).subscribe() + AndroidKolibriServerPlugin(self, self.port).subscribe() - ZipContentServerPlugin(self, self.zip_port).subscribe() + AndroidZipContentServerPlugin(self, self.zip_port).subscribe() def get_app_key(self): return DeviceAppKey.get_app_key() @@ -71,3 +71,19 @@ def SERVING(self, port): next_url = self.application.get_saved_kolibri_path() or "" start_url = urljoin(base_url, next_url) self.application.replace_url(start_url) + + +class AndroidKolibriServerPlugin(KolibriServerPlugin): + @property + def application(self): + from kolibri_android.kolibri_extra.wsgi import application + + return application + + +class AndroidZipContentServerPlugin(ZipContentServerPlugin): + @property + def application(self): + from kolibri_android.kolibri_extra.alt_wsgi import alt_application + + return alt_application