-from os.path import join, abspath, exists
-from django.db import models
-from waiter.settings import WAITER_ROOT, WAITER_URL
+from os.path import join, isfile
from django.core.urlresolvers import reverse
+from django.db import models
+from waiter.settings import WAITER_URL, WAITER_MAX_QUEUE
+from waiter.utils import check_abspath
+from picklefield import PickledObjectField
+
class WaitedFile(models.Model):
path = models.CharField(max_length=255, unique=True, db_index=True)
- task = models.CharField(max_length=64, null=True, editable=False)
+ task_id = models.CharField(max_length=128, db_index=True, null=True, blank=True)
+ task = PickledObjectField(null=True, editable=False)
description = models.CharField(max_length=255, null=True, blank=True)
- @staticmethod
- def abspath(path):
- abs_path = abspath(join(WAITER_ROOT, path))
- if not abs_path.startswith(WAITER_ROOT):
- raise ValueError('Path not inside WAITER_ROOT.')
- return abs_path
-
@classmethod
def exists(cls, path):
"""Returns opened file or None.
`path` is relative to WAITER_ROOT.
Won't open a path leading outside of WAITER_ROOT.
"""
- abs_path = cls.abspath(path)
- # Pre-fetch objects to avoid minor race condition
+ abs_path = check_abspath(path)
+ # Pre-fetch objects for deletion to avoid minor race condition
relevant = [o.id for o in cls.objects.filter(path=path)]
- print abs_path
- if exists(abs_path):
+ if isfile(abs_path):
cls.objects.filter(id__in=relevant).delete()
return True
else:
return False
+ @classmethod
+ def can_order(cls, path):
+ return (cls.objects.filter(path=path).exists() or
+ cls.exists(path) or
+ cls.objects.count() < WAITER_MAX_QUEUE
+ )
+
+ def is_stale(self):
+ if self.task is None:
+ # Race; just let the other task roll.
+ return False
+ if self.task.status not in (u'PENDING', u'STARTED', u'SUCCESS', u'RETRY'):
+ return True
+ return False
+
@classmethod
def order(cls, path, task_creator, description=None):
"""
Returns an URL for the user to follow.
If the file is ready, returns download URL.
If not, starts preparing it and returns waiting URL.
+
+ task_creator: function taking a path and generating the file;
+ description: a string or string proxy with a description for user;
"""
already = cls.exists(path)
if not already:
waited, created = cls.objects.get_or_create(path=path)
- if created:
- # TODO: makedirs
- waited.task = task_creator(cls.abspath(path))
- print waited.task
+ if created or waited.is_stale():
+ waited.task = task_creator(check_abspath(path))
+ waited.task_id = waited.task.task_id
waited.description = description
waited.save()
- # TODO: it the task exists, if stale delete, send some mail and restart
return reverse("waiter", args=[path])
return join(WAITER_URL, path)