team-3/src/c3nav/mapdata/views.py

187 lines
6.7 KiB
Python
Raw Normal View History

import base64
2017-10-20 16:47:53 +02:00
import os
from shutil import rmtree
from wsgiref.util import FileWrapper
2017-10-20 16:47:53 +02:00
from django.conf import settings
2017-10-24 18:12:46 +02:00
from django.core.cache import cache
2017-10-24 13:59:42 +02:00
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponse, HttpResponseNotModified, StreamingHttpResponse
2017-10-24 13:59:42 +02:00
from django.shortcuts import get_object_or_404
2017-10-24 21:40:10 +02:00
from django.views.decorators.http import etag
2017-10-10 14:39:11 +02:00
2017-10-24 21:03:56 +02:00
from c3nav.mapdata.middleware import no_language
from c3nav.mapdata.models import Level, MapUpdate
from c3nav.mapdata.models.access import AccessPermission
2017-11-06 11:18:45 +01:00
from c3nav.mapdata.render.engines import ImageRenderEngine
from c3nav.mapdata.render.renderer import MapRenderer
from c3nav.mapdata.utils.cache import CachePackage, MapHistory
from c3nav.mapdata.utils.tiles import (build_access_cache_key, build_base_cache_key, build_tile_access_cookie,
build_tile_etag, get_tile_bounds, parse_tile_access_cookie)
def set_tile_access_cookie(request, response):
access_permissions = AccessPermission.get_for_request(request)
if access_permissions:
cookie = build_tile_access_cookie(access_permissions, settings.SECRET_TILE_KEY)
response.set_cookie(settings.TILE_ACCESS_COOKIE_NAME, cookie, max_age=60,
domain=settings.TILE_ACCESS_COOKIE_DOMAIN,
httponly=settings.TILE_ACCESS_COOKIE_HTTPONLY,
secure=settings.TILE_ACCESS_COOKIE_SECURE)
else:
response.delete_cookie(settings.TILE_ACCESS_COOKIE_NAME)
response['Cache-Control'] = 'no-cache'
2017-10-10 14:39:11 +02:00
encoded_tile_secret = base64.b64encode(settings.SECRET_TILE_KEY.encode()).decode()
def enforce_tile_secret_auth(request):
x_tile_secret = request.META.get('HTTP_X_TILE_SECRET')
if x_tile_secret:
if x_tile_secret != encoded_tile_secret:
raise PermissionDenied
elif not request.user.is_superuser:
raise PermissionDenied
2017-10-24 21:03:56 +02:00
@no_language()
def tile(request, level, zoom, x, y, access_permissions=None):
if access_permissions is not None:
enforce_tile_secret_auth(request)
elif settings.TILE_CACHE_SERVER:
return HttpResponse('use %s instead of /map/' % settings.TILE_CACHE_SERVER,
status=400, content_type='text/plain')
2017-10-10 14:39:11 +02:00
zoom = int(zoom)
2017-11-28 14:14:03 +01:00
if not (-2 <= zoom <= 5):
2017-10-10 14:39:11 +02:00
raise Http404
cache_package = CachePackage.open_cached()
# check if bounds are valid
x = int(x)
y = int(y)
minx, miny, maxx, maxy = get_tile_bounds(zoom, x, y)
if not cache_package.bounds_valid(minx, miny, maxx, maxy):
2017-10-10 14:39:11 +02:00
raise Http404
# get level
2017-10-24 18:12:46 +02:00
level = int(level)
level_data = cache_package.levels.get(level)
if level_data is None:
2017-10-24 18:12:46 +02:00
raise Http404
2017-10-24 23:41:53 +02:00
# decode access permissions
if access_permissions is None:
try:
cookie = request.COOKIES[settings.TILE_ACCESS_COOKIE_NAME]
except KeyError:
access_permissions = set()
else:
access_permissions = parse_tile_access_cookie(cookie, settings.SECRET_TILE_KEY)
2017-12-20 19:11:28 +01:00
access_permissions &= set(level_data.restrictions[minx:maxx, miny:maxy])
else:
access_permissions = set(int(i) for i in access_permissions.split('-')) - {0}
# build cache keys
last_update = level_data.history.last_update(minx, miny, maxx, maxy)
base_cache_key = build_base_cache_key(last_update)
access_cache_key = build_access_cache_key(access_permissions)
2017-10-20 16:21:25 +02:00
2017-10-24 18:12:46 +02:00
# check browser cache
tile_etag = build_tile_etag(level, zoom, x, y, base_cache_key, access_cache_key, settings.SECRET_TILE_KEY)
2017-10-20 16:21:25 +02:00
if_none_match = request.META.get('HTTP_IF_NONE_MATCH')
if if_none_match == tile_etag:
2017-10-20 16:21:25 +02:00
return HttpResponseNotModified()
2017-10-24 18:12:46 +02:00
data = None
tile_dirname, last_update_filename, tile_filename, tile_cache_update_cache_key = '', '', '', ''
2017-10-20 16:47:53 +02:00
2017-10-24 18:12:46 +02:00
# get tile cache last update
if settings.CACHE_TILES:
tile_dirname = settings.TILES_ROOT / str(level) / str(zoom) / str(x) / str(y)
last_update_filename = tile_dirname / 'last_update'
tile_filename = tile_dirname / (access_cache_key+'.png')
2017-10-24 18:12:46 +02:00
# get tile cache last update
2017-11-21 05:52:19 +01:00
tile_cache_update_cache_key = 'mapdata:tile-cache-update:%d-%d-%d-%d' % (level, zoom, x, y)
2017-10-24 18:12:46 +02:00
tile_cache_update = cache.get(tile_cache_update_cache_key, None)
if tile_cache_update is None:
try:
with open(last_update_filename) as f:
tile_cache_update = f.read()
except FileNotFoundError:
pass
if tile_cache_update != base_cache_key:
rmtree(tile_dirname)
2017-10-24 18:12:46 +02:00
else:
try:
with open(tile_filename, 'rb') as f:
data = f.read()
except FileNotFoundError:
pass
2017-10-20 16:47:53 +02:00
2017-10-24 18:12:46 +02:00
if data is None:
renderer = MapRenderer(level, minx, miny, maxx, maxy, scale=2 ** zoom, access_permissions=access_permissions)
2017-11-09 17:42:19 +01:00
image = renderer.render(ImageRenderEngine)
data = image.render()
2017-10-20 16:47:53 +02:00
if settings.CACHE_TILES:
2017-10-24 18:12:46 +02:00
os.makedirs(tile_dirname, exist_ok=True)
2017-11-09 17:42:19 +01:00
with open(tile_filename, 'wb') as f:
2017-10-20 16:47:53 +02:00
f.write(data)
2017-10-24 18:12:46 +02:00
with open(last_update_filename, 'w') as f:
f.write(base_cache_key)
2017-12-10 15:36:13 +01:00
cache.set(tile_cache_update_cache_key, base_cache_key, 60)
2017-10-10 14:39:11 +02:00
2017-11-09 17:42:19 +01:00
response = HttpResponse(data, 'image/png')
response['ETag'] = tile_etag
2017-10-20 16:21:25 +02:00
response['Cache-Control'] = 'no-cache'
2017-10-24 23:41:53 +02:00
response['Vary'] = 'Cookie'
2017-10-20 16:21:25 +02:00
2017-10-10 14:39:11 +02:00
return response
2017-10-24 13:59:42 +02:00
@etag(lambda *args, **kwargs: MapUpdate.current_processed_cache_key())
2017-10-24 21:06:42 +02:00
@no_language()
def map_history(request, level, mode, filetype):
2017-10-24 13:59:42 +02:00
if not request.user.is_superuser:
raise PermissionDenied
level = get_object_or_404(Level, pk=level)
if mode == 'composite' and level.on_top_of_id is not None:
2017-10-24 13:59:42 +02:00
raise Http404
2017-10-24 14:46:35 +02:00
history = MapHistory.open_level(level.pk, mode)
if filetype == 'png':
2017-10-24 13:59:42 +02:00
response = HttpResponse(content_type='image/png')
history.to_image().save(response, format='PNG')
elif filetype == 'data':
2017-10-24 13:59:42 +02:00
response = HttpResponse(content_type='application/octet-stream')
history.write(response)
else:
raise ValueError
2017-10-24 21:40:10 +02:00
response['Cache-Control'] = 'no-cache'
2017-10-24 13:59:42 +02:00
return response
@etag(lambda *args, **kwargs: MapUpdate.current_processed_cache_key())
@no_language()
def get_cache_package(request, filetype):
enforce_tile_secret_auth(request)
filename = settings.CACHE_ROOT / ('package.'+filetype)
f = open(filename, 'rb')
f.seek(0, os.SEEK_END)
size = f.tell()
f.seek(0)
content_type = 'application/' + {'tar': 'x-tar', 'tar.gz': 'gzip', 'tar.xz': 'x-xz'}[filetype]
response = StreamingHttpResponse(FileWrapper(f), content_type=content_type)
response['Content-Length'] = size
return response