Commiting changes to funding.css
[wolnelektury.git] / apps / waiter / models.py
index fd97301..10f9289 100644 (file)
@@ -1,20 +1,17 @@
-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.
@@ -22,32 +19,47 @@ class WaitedFile(models.Model):
         `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)