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