62a1216b441e5f2d1b8bd77e97d5eef99489e424
[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, youtube_status=None)
223     objects_by_status = {}
224     for o in objects:
225         statuses = set()
226         if o.mp3_status:
227             statuses.add((o.mp3_status, o.get_mp3_status_display()))
228         if o.ogg_status:
229             statuses.add((o.ogg_status, o.get_ogg_status_display()))
230         if o.youtube_status:
231             statuses.add((o.youtube_status, o.get_youtube_status_display()))
232         for status in statuses:
233             objects_by_status.setdefault(status, []).append(o)
234     status_objects = sorted(objects_by_status.items(), reverse=True)
235
236     return render(request, "archive/list_publishing.html", locals())
237
238
239 def list_published(request):
240     division = 'published'
241
242     objects = models.Audiobook.objects.exclude(Q(mp3_published=None) | Q(ogg_published=None))
243     return render(request, "archive/list_published.html", locals())
244
245
246 @permission_required('archive.change_audiobook')
247 def file_managed(request, id):
248     audiobook = get_object_or_404(models.Audiobook, id=id)
249
250     if request.POST:
251         form = AudiobookForm(request.POST, instance=audiobook)
252         if form.is_valid():
253             try:
254                 form.save()
255             except IOError:
256                 raise Http404
257
258     division = 'published' if audiobook.published() else 'unpublished'
259     path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
260
261     # for tags update
262     tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
263     if not tags:
264         tags = {}
265     form = AudiobookForm(instance=audiobook)
266
267     user_can_publish = (
268         request.user.is_authenticated and
269         request.user.oauthconnection_set.filter(access=True).exists())
270
271     return render(request, "archive/file_managed.html", locals())
272
273
274 def list_unmanaged(request):
275     division = 'unmanaged'
276
277     objects = sorted(all_files(settings.UNMANAGED_PATH))
278     return render(request, "archive/list_unmanaged.html", locals())
279
280
281 def file_unmanaged(request, filename):
282     division = 'unmanaged'
283
284     tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
285     if not tags:
286         tags = {}
287     
288     err_exists = request.GET.get('exists')
289     return render(request, "archive/file_unmanaged.html", locals())