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