2017-11-16 20:54:59 +01:00
|
|
|
import logging
|
2017-11-16 02:47:28 +01:00
|
|
|
import os
|
2017-11-10 16:32:58 +01:00
|
|
|
import pickle
|
2017-11-16 02:47:28 +01:00
|
|
|
from contextlib import contextmanager, suppress
|
2017-07-05 22:35:14 +02:00
|
|
|
|
2017-07-05 22:04:22 +02:00
|
|
|
from django.conf import settings
|
2017-07-05 22:35:14 +02:00
|
|
|
from django.core.cache import cache
|
|
|
|
from django.db import models, transaction
|
2017-07-05 23:38:47 +02:00
|
|
|
from django.utils.http import int_to_base36
|
|
|
|
from django.utils.timezone import make_naive
|
2017-07-05 22:04:22 +02:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
|
2017-11-10 16:56:50 +01:00
|
|
|
from c3nav.mapdata.tasks import process_map_updates
|
|
|
|
|
2017-07-05 22:04:22 +02:00
|
|
|
|
|
|
|
class MapUpdate(models.Model):
|
|
|
|
"""
|
|
|
|
A map update. created whenever mapdata is changed.
|
|
|
|
"""
|
|
|
|
datetime = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
|
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT)
|
|
|
|
type = models.CharField(max_length=32)
|
2017-11-10 16:32:58 +01:00
|
|
|
processed = models.BooleanField(default=False)
|
2017-11-14 23:45:53 +01:00
|
|
|
|
2017-07-05 22:04:22 +02:00
|
|
|
class Meta:
|
|
|
|
verbose_name = _('Map update')
|
|
|
|
verbose_name_plural = _('Map updates')
|
|
|
|
default_related_name = 'mapupdates'
|
|
|
|
get_latest_by = 'datetime'
|
|
|
|
|
2017-11-10 16:32:58 +01:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.was_processed = self.processed
|
|
|
|
|
2017-07-05 22:35:14 +02:00
|
|
|
@classmethod
|
|
|
|
def last_update(cls):
|
|
|
|
last_update = cache.get('mapdata:last_update', None)
|
|
|
|
if last_update is not None:
|
|
|
|
return last_update
|
|
|
|
with cls.lock():
|
2017-07-05 23:38:47 +02:00
|
|
|
last_update = cls.objects.latest()
|
2017-11-10 16:32:58 +01:00
|
|
|
result = last_update.to_tuple
|
2017-12-19 22:27:17 +01:00
|
|
|
cache.set('mapdata:last_update', result, 300)
|
2017-10-24 01:59:50 +02:00
|
|
|
return result
|
|
|
|
|
2017-11-10 16:32:58 +01:00
|
|
|
@classmethod
|
|
|
|
def last_processed_update(cls):
|
|
|
|
last_processed_update = cache.get('mapdata:last_processed_update', None)
|
|
|
|
if last_processed_update is not None:
|
|
|
|
return last_processed_update
|
|
|
|
with cls.lock():
|
|
|
|
last_processed_update = cls.objects.filter(processed=True).latest()
|
|
|
|
result = last_processed_update.to_tuple
|
2017-12-19 22:27:17 +01:00
|
|
|
cache.set('mapdata:last_processed_update', result, 300)
|
2017-11-10 16:32:58 +01:00
|
|
|
return result
|
|
|
|
|
2017-10-24 01:59:50 +02:00
|
|
|
@property
|
|
|
|
def to_tuple(self):
|
|
|
|
return self.pk, int(make_naive(self.datetime).timestamp())
|
2017-07-05 23:38:47 +02:00
|
|
|
|
2017-10-23 22:49:45 +02:00
|
|
|
@property
|
|
|
|
def cache_key(self):
|
2017-10-24 18:12:46 +02:00
|
|
|
return self.build_cache_key(self.pk, int(make_naive(self.datetime).timestamp()))
|
2017-10-23 22:49:45 +02:00
|
|
|
|
2017-07-05 23:38:47 +02:00
|
|
|
@classmethod
|
2017-12-04 17:41:17 +01:00
|
|
|
def current_cache_key(cls, request=None):
|
2017-10-24 18:12:46 +02:00
|
|
|
return cls.build_cache_key(*cls.last_update())
|
|
|
|
|
2017-11-10 16:32:58 +01:00
|
|
|
@classmethod
|
2017-12-04 17:41:17 +01:00
|
|
|
def current_processed_cache_key(cls, request=None):
|
2017-11-10 16:32:58 +01:00
|
|
|
return cls.build_cache_key(*cls.last_processed_update())
|
|
|
|
|
2017-10-24 18:12:46 +02:00
|
|
|
@staticmethod
|
|
|
|
def build_cache_key(pk, timestamp):
|
2017-10-24 01:59:50 +02:00
|
|
|
return int_to_base36(pk)+'_'+int_to_base36(timestamp)
|
2017-07-05 22:35:14 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@contextmanager
|
|
|
|
def lock(cls):
|
|
|
|
with transaction.atomic():
|
2017-12-20 13:13:49 +01:00
|
|
|
yield cls.objects.select_for_update().get(pk=cls.objects.earliest().pk)
|
2017-07-05 22:35:14 +02:00
|
|
|
|
2017-11-16 02:47:28 +01:00
|
|
|
def _changed_geometries_filename(self):
|
|
|
|
return os.path.join(settings.CACHE_ROOT, 'changed_geometries', 'update_%d.pickle' % self.pk)
|
|
|
|
|
2017-11-10 16:32:58 +01:00
|
|
|
@classmethod
|
|
|
|
def process_updates(cls):
|
2017-11-16 20:54:59 +01:00
|
|
|
logger = logging.getLogger('c3nav')
|
|
|
|
|
2017-11-10 23:23:22 +01:00
|
|
|
with transaction.atomic():
|
2017-11-16 02:47:28 +01:00
|
|
|
new_updates = tuple(cls.objects.filter(processed=False).select_for_update(nowait=True))
|
2017-11-10 16:32:58 +01:00
|
|
|
if not new_updates:
|
|
|
|
return ()
|
2017-10-19 14:41:42 +02:00
|
|
|
|
2017-11-21 02:12:00 +01:00
|
|
|
from c3nav.mapdata.utils.cache.changes import changed_geometries
|
2017-11-16 19:24:49 +01:00
|
|
|
changed_geometries.reset()
|
|
|
|
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.info('Recalculating altitude areas...')
|
|
|
|
|
2017-11-16 19:24:49 +01:00
|
|
|
from c3nav.mapdata.models import AltitudeArea
|
|
|
|
AltitudeArea.recalculate()
|
|
|
|
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.info('%.3f m² of altitude areas affected.' % changed_geometries.area)
|
|
|
|
|
2017-11-17 14:59:46 +01:00
|
|
|
last_processed_update = cls.objects.filter(processed=True).latest().to_tuple
|
|
|
|
|
2017-11-10 17:58:39 +01:00
|
|
|
for new_update in new_updates:
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.info('Applying changed geometries from MapUpdate #%(id)s (%(type)s)...' %
|
|
|
|
{'id': new_update.pk, 'type': new_update.type})
|
2017-11-16 19:24:49 +01:00
|
|
|
try:
|
2017-11-16 20:54:59 +01:00
|
|
|
new_changes = pickle.load(open(new_update._changed_geometries_filename(), 'rb'))
|
2017-11-16 19:24:49 +01:00
|
|
|
except FileNotFoundError:
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.warning('changed_geometries pickle file not found.')
|
|
|
|
else:
|
|
|
|
logger.info('%.3f m² affected by this update.' % new_changes.area)
|
|
|
|
changed_geometries.combine(new_changes)
|
2017-11-10 17:58:39 +01:00
|
|
|
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.info('%.3f m² of geometries affected in total.' % changed_geometries.area)
|
|
|
|
|
2017-11-16 19:24:49 +01:00
|
|
|
changed_geometries.save(last_processed_update, new_updates[-1].to_tuple)
|
2017-10-24 00:11:02 +02:00
|
|
|
|
2017-11-16 20:54:59 +01:00
|
|
|
logger.info('Rebuilding level render data...')
|
|
|
|
|
2017-11-20 02:53:49 +01:00
|
|
|
from c3nav.mapdata.render.renderdata import LevelRenderData
|
2017-11-10 16:32:58 +01:00
|
|
|
LevelRenderData.rebuild()
|
2017-10-24 00:11:02 +02:00
|
|
|
|
2017-11-27 16:53:56 +01:00
|
|
|
logger.info('Rebuilding router...')
|
|
|
|
from c3nav.routing.router import Router
|
|
|
|
Router.rebuild()
|
|
|
|
|
2017-12-20 20:14:00 +01:00
|
|
|
for new_update in new_updates:
|
|
|
|
new_update.processed = True
|
|
|
|
new_update.save()
|
|
|
|
|
2017-11-15 23:01:43 +01:00
|
|
|
transaction.on_commit(
|
2017-12-20 20:16:28 +01:00
|
|
|
lambda: cache.set('mapdata:last_processed_update', new_updates[-1].to_tuple, 300)
|
2017-11-15 23:01:43 +01:00
|
|
|
)
|
2017-11-10 16:32:58 +01:00
|
|
|
|
|
|
|
return new_updates
|
|
|
|
|
|
|
|
def save(self, **kwargs):
|
2017-11-10 16:56:50 +01:00
|
|
|
new = self.pk is None
|
|
|
|
if not new and (self.was_processed or not self.processed):
|
2017-11-10 16:32:58 +01:00
|
|
|
raise TypeError
|
2017-10-24 00:11:02 +02:00
|
|
|
|
2017-11-10 16:32:58 +01:00
|
|
|
super().save(**kwargs)
|
2017-10-24 01:14:36 +02:00
|
|
|
|
2017-11-16 02:47:28 +01:00
|
|
|
with suppress(FileExistsError):
|
|
|
|
os.mkdir(os.path.dirname(self._changed_geometries_filename()))
|
|
|
|
|
2017-11-21 02:12:00 +01:00
|
|
|
from c3nav.mapdata.utils.cache.changes import changed_geometries
|
2017-11-16 02:47:28 +01:00
|
|
|
pickle.dump(changed_geometries, open(self._changed_geometries_filename(), 'wb'))
|
|
|
|
|
2017-12-14 00:23:58 +01:00
|
|
|
if new:
|
2017-11-15 23:01:43 +01:00
|
|
|
transaction.on_commit(
|
2017-12-19 22:27:17 +01:00
|
|
|
lambda: cache.set('mapdata:last_update', self.to_tuple, 300)
|
2017-11-15 23:01:43 +01:00
|
|
|
)
|
2017-12-14 00:23:58 +01:00
|
|
|
if settings.HAS_CELERY:
|
|
|
|
transaction.on_commit(
|
|
|
|
lambda: process_map_updates.delay()
|
|
|
|
)
|