Missing import.
[audio.git] / src / archive / views.py
1 from datetime import datetime
2 import os
3 import os.path
4 from urllib.parse import quote
5
6 from archive import settings
7 from django.contrib.auth.decorators import permission_required
8 from django.urls import reverse
9 from django.db.models import Q, Max
10 from django.http import Http404, HttpResponse
11 from django.shortcuts import render, redirect, get_object_or_404
12 from django.utils.translation import gettext as _
13 from django.views.decorators.http import require_POST
14
15 import mutagen
16
17 from archive.constants import status
18 from archive import models
19 from archive.forms import AudiobookForm
20 from archive import tasks
21 from archive.utils import all_files
22
23
24 def list_new(request):
25     division = 'new'
26
27     path = settings.NEW_PATH
28     objects = sorted(all_files(path))
29     return render(request, "archive/list_new.html", locals())
30
31
32 @permission_required('archive.change_audiobook')
33 def file_new(request, filename):
34     division = 'new'
35
36     filepath = filename
37     root_filepath = os.path.join(settings.NEW_PATH, filename)
38     if request.POST:
39         form = AudiobookForm(request.POST)
40         if form.is_valid():
41             try:
42                 form.save(path=filepath)
43             except IOError:
44                 raise Http404
45             return redirect(list_new)
46
47     try:
48         tags = mutagen.File(root_filepath)
49     except IOError:
50         raise Http404
51     d = {}
52     if tags:
53         for tag in tags:
54             value = tags[tag]
55             if isinstance(value, list):
56                 d[tag] = value[0]
57             else:
58                 d[tag] = value
59             if tag == 'project':
60                 try:
61                     d[tag] = models.Project.objects.get(name=d[tag]).pk
62                 except models.Project.DoesNotExist:
63                     d[tag] = None
64
65     if not request.POST:
66         form = AudiobookForm(initial=d)
67     return render(request, "archive/file_new.html", locals())
68
69
70 @require_POST
71 @permission_required('archive.change_audiobook')
72 def move_to_archive(request, filename):
73     """ move a new file to the unmanaged files dir """
74
75     filename_str = filename.encode('utf-8')
76     old_path = os.path.join(settings.NEW_PATH, filename_str)
77     new_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
78     new_dir = os.path.split(new_path)[0]
79     if not os.path.isdir(new_dir):
80         os.makedirs(new_dir)
81
82     if not os.path.isfile(old_path):
83         raise Http404
84
85     try:
86         os.link(old_path, new_path)
87         os.unlink(old_path)
88     except OSError:
89         # destination file exists, don't overwrite it
90         # TODO: this should probably be more informative
91         return redirect(file_new, filename)
92
93     return redirect(list_new)
94
95
96 @require_POST
97 @permission_required('archive.change_audiobook')
98 def remove_to_archive(request, aid):
99     """ move a managed file to the unmanaged files dir """
100
101     audiobook = get_object_or_404(models.Audiobook, id=aid)
102     old_path = audiobook.source_file.path
103     new_path = os.path.join(settings.UNMANAGED_PATH,
104         str(audiobook.source_file)[len(settings.FILES_SAVE_PATH):].lstrip('/'))
105     new_dir = os.path.split(new_path)[0]
106     if not os.path.isdir(new_dir):
107         os.makedirs(new_dir)
108
109     if not os.path.isfile(old_path):
110         raise Http404
111
112     success = False
113     try_new_path = new_path
114     try_number = 0
115     while not success:
116         try:
117             os.link(old_path, try_new_path)
118         except OSError:
119             # destination file exists, don't overwrite it
120             try_number += 1
121             parts = new_path.rsplit('.', 1)
122             parts[0] += '_%d' % try_number
123             try_new_path = ".".join(parts)
124         else:
125             os.unlink(old_path)
126             audiobook.delete()
127             success = True
128
129     return redirect(list_unmanaged)
130
131 @require_POST
132 @permission_required('archive.change_audiobook')
133 def move_to_new(request, filename):
134     """ move a unmanaged file to new files dir """
135
136     filename_str = filename.encode('utf-8')
137     old_path = os.path.join(settings.UNMANAGED_PATH, filename_str)
138     new_path = os.path.join(settings.NEW_PATH, filename_str)
139     new_dir = os.path.split(new_path)[0]
140     if not os.path.isdir(new_dir):
141         os.makedirs(new_dir)
142
143     if not os.path.isfile(old_path):
144         raise Http404
145
146     try:
147         os.link(old_path, new_path)
148         os.unlink(old_path)
149     except OSError:
150         # destination file exists, don't overwrite it
151         # TODO: this should probably be more informative
152         return redirect(reverse(file_unmanaged, args=[filename]) + "?exists=1")
153
154     return redirect(list_unmanaged)
155
156
157 @require_POST
158 @permission_required('archive.change_audiobook')
159 def publish(request, aid, publish=True):
160     """ mark file for publishing """
161     audiobook = get_object_or_404(models.Audiobook, id=aid)
162     tags = {
163         'name': audiobook.title,
164         'url': audiobook.url,
165         'tags': audiobook.new_publish_tags(),
166         }
167     audiobook.set_mp3_tags(tags)
168     audiobook.set_ogg_tags(tags)
169     audiobook.mp3_status = audiobook.ogg_status = status.WAITING
170     audiobook.save()
171     # isn't there a race here?
172     audiobook.mp3_task = tasks.Mp3Task.delay(request.user.id, aid, publish).task_id
173     audiobook.ogg_task = tasks.OggTask.delay(request.user.id, aid, publish).task_id
174     audiobook.save()
175
176     return redirect(file_managed, aid)
177
178
179 @require_POST
180 @permission_required('archive.change_audiobook')
181 def cancel_publishing(request, aid):
182     """ cancel scheduled publishing """
183     audiobook = get_object_or_404(models.Audiobook, id=aid)
184     # TODO: cancel tasks
185     audiobook.mp3_status = None
186     audiobook.ogg_status = None
187     audiobook.youtube_status = None
188     audiobook.save()
189     return redirect(file_managed, aid)
190
191
192 def download(request, aid, which="source"):
193     if which not in ("source", "mp3", "ogg", 'mkv'):
194         raise Http404
195     audiobook = get_object_or_404(models.Audiobook, id=aid)
196     field = which
197     if which == 'mkv':
198         field = 'youtube'
199     file_ = getattr(audiobook, "%s_file" % field)
200     if not file_:
201         raise Http404
202     ext = file_.path.rsplit('.', 1)[-1]
203     response = HttpResponse(content_type='application/force-download')
204     
205     response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
206         quote(audiobook.title.encode('utf-8'), safe=''), ext)
207     with open(file_.path, 'rb') as f:
208         response.write(f.read())
209     #response['X-Sendfile'] = file_.path.encode('utf-8')
210     return response
211
212
213 def list_unpublished(request):
214     division = 'unpublished'
215
216     objects = models.Audiobook.objects.filter(Q(mp3_published=None) | Q(ogg_published=None))
217     return render(request, "archive/list_unpublished.html", locals())
218
219
220 def list_publishing(request):
221     division = 'publishing'
222
223     objects = models.Audiobook.objects.exclude(mp3_status=None, ogg_status=None, youtube_status=None)
224     objects_by_status = {}
225     for o in objects:
226         statuses = set()
227         if o.mp3_status:
228             statuses.add((o.mp3_status, o.get_mp3_status_display()))
229         if o.ogg_status:
230             statuses.add((o.ogg_status, o.get_ogg_status_display()))
231         if o.youtube_status:
232             statuses.add((o.youtube_status, o.get_youtube_status_display()))
233         for status in statuses:
234             objects_by_status.setdefault(status, []).append(o)
235     status_objects = sorted(objects_by_status.items(), reverse=True)
236
237     return render(request, "archive/list_publishing.html", locals())
238
239
240 def list_published(request):
241     division = 'published'
242
243     objects = models.Audiobook.objects.exclude(Q(mp3_published=None) | Q(ogg_published=None))
244     return render(request, "archive/list_published.html", locals())
245
246
247 @permission_required('archive.change_audiobook')
248 def file_managed(request, id):
249     audiobook = get_object_or_404(models.Audiobook, id=id)
250
251     if request.POST:
252         form = AudiobookForm(request.POST, instance=audiobook)
253         if form.is_valid():
254             try:
255                 form.save()
256             except IOError:
257                 raise Http404
258
259     division = 'published' if audiobook.published() else 'unpublished'
260     path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
261
262     # for tags update
263     tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
264     if not tags:
265         tags = {}
266     form = AudiobookForm(instance=audiobook)
267
268     user_can_publish = (
269         request.user.is_authenticated and
270         request.user.oauthconnection_set.filter(access=True).exists())
271
272     alerts = []
273     series = models.Audiobook.objects.filter(url=audiobook.url)
274     real = series.count()
275     if real != audiobook.parts_count:
276         alerts.append(_('Parts number inconsitent. Declared number: %(declared)d. Real number: %(real)d') % {"declared": audiobook.parts_count, "real": real})
277     if audiobook.parts_count > 1:
278         if not audiobook.index:
279             alerts.append(_('There is more than one part, but index is not set.'))
280         if set(series.values_list('index', flat=True)) != set(range(1, audiobook.parts_count + 1)):
281             alerts.append(_('Part indexes are not 1..%(parts_count)d.') % {"parts_count": audiobook.parts_count})
282
283     return render(request, "archive/file_managed.html", locals())
284
285
286 def list_unmanaged(request):
287     division = 'unmanaged'
288
289     objects = sorted(all_files(settings.UNMANAGED_PATH))
290     return render(request, "archive/list_unmanaged.html", locals())
291
292
293 def file_unmanaged(request, filename):
294     division = 'unmanaged'
295
296     tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
297     if not tags:
298         tags = {}
299     
300     err_exists = request.GET.get('exists')
301     return render(request, "archive/file_unmanaged.html", locals())