Merge branch 'reflow'
[wolnelektury.git] / src / waiter / models.py
1 # -*- coding: utf-8 -*-
2 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
4 #
5 from os.path import join, isfile
6 from django.core.urlresolvers import reverse
7 from django.db import models
8 from waiter.settings import WAITER_URL, WAITER_MAX_QUEUE
9 from waiter.utils import check_abspath
10 from picklefield import PickledObjectField
11
12
13 class WaitedFile(models.Model):
14     path = models.CharField(max_length=255, unique=True, db_index=True)
15     task_id = models.CharField(max_length=128, db_index=True, null=True, blank=True)
16     task = PickledObjectField(null=True, editable=False)
17     description = models.CharField(max_length=255, null=True, blank=True)
18
19     @classmethod
20     def exists(cls, path):
21         """Returns opened file or None.
22
23         `path` is relative to WAITER_ROOT.
24         Won't open a path leading outside of WAITER_ROOT.
25         """
26         abs_path = check_abspath(path)
27         # Pre-fetch objects for deletion to avoid minor race condition
28         relevant = [o.id for o in cls.objects.filter(path=path)]
29         if isfile(abs_path):
30             cls.objects.filter(id__in=relevant).delete()
31             return True
32         else:
33             return False
34
35     @classmethod
36     def can_order(cls, path):
37         return (cls.objects.filter(path=path).exists() or
38                 cls.exists(path) or
39                 cls.objects.count() < WAITER_MAX_QUEUE
40                 )
41
42     @classmethod
43     def order(cls, path, task_creator, description=None):
44         """
45         Returns an URL for the user to follow.
46         If the file is ready, returns download URL.
47         If not, starts preparing it and returns waiting URL.
48
49         task_creator: function taking a path and generating the file;
50         description: a string or string proxy with a description for user;
51         """
52         already = cls.exists(path)
53         if not already:
54             waited, created = cls.objects.get_or_create(path=path)
55             if created or waited.is_stale():
56                 waited.task = task_creator(check_abspath(path), waited.pk)
57                 waited.task_id = waited.task.task_id
58                 waited.description = description
59                 waited.save()
60             return reverse("waiter", args=[path])
61         return join(WAITER_URL, path)