Updates.
[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 import logout
8 from django.contrib.auth.decorators import permission_required
9 from django.urls import reverse
10 from django.db.models import Q, Max
11 from django.http import Http404, HttpResponse
12 from django.shortcuts import render, redirect, get_object_or_404
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.save()
188     return redirect(file_managed, aid)
189
190
191 def download(request, aid, which="source"):
192     if which not in ("source", "mp3", "ogg"):
193         raise Http404
194     audiobook = get_object_or_404(models.Audiobook, id=aid)
195     file_ = getattr(audiobook, "%s_file" % which)
196     if not file_:
197         raise Http404
198     ext = file_.path.rsplit('.', 1)[-1]
199     response = HttpResponse(content_type='application/force-download')
200     
201     response['Content-Disposition'] = "attachment; filename*=UTF-8''%s.%s" % (
202         quote(audiobook.title.encode('utf-8'), safe=''), ext)
203     response['X-Sendfile'] = file_.path.encode('utf-8')
204     return response
205
206
207 def list_unpublished(request):
208     division = 'unpublished'
209
210     objects = models.Audiobook.objects.filter(Q(mp3_published=None) | Q(ogg_published=None))
211     return render(request, "archive/list_unpublished.html", locals())
212
213
214 def list_publishing(request):
215     division = 'publishing'
216
217     objects = models.Audiobook.objects.exclude(mp3_status=None, ogg_status=None)
218     objects_by_status = {}
219     for o in objects:
220         if o.mp3_status:
221             k = o.mp3_status, o.get_mp3_status_display()
222             objects_by_status.setdefault(k, []).append(o)
223         if o.ogg_status and o.ogg_status != o.mp3_status:
224             k = o.ogg_status, o.get_ogg_status_display()
225             objects_by_status.setdefault(k, []).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 def list_published(request):
232     division = 'published'
233
234     objects = models.Audiobook.objects.exclude(Q(mp3_published=None) | Q(ogg_published=None))
235     return render(request, "archive/list_published.html", locals())
236
237
238 @permission_required('archive.change_audiobook')
239 def file_managed(request, id):
240     audiobook = get_object_or_404(models.Audiobook, id=id)
241
242     if request.POST:
243         form = AudiobookForm(request.POST, instance=audiobook)
244         if form.is_valid():
245             try:
246                 form.save()
247             except IOError:
248                 raise Http404
249
250     division = 'published' if audiobook.published() else 'unpublished'
251     path = audiobook.source_file.path[len(settings.FILES_PATH):].lstrip('/')
252
253     # for tags update
254     tags = mutagen.File(audiobook.source_file.path.encode('utf-8'))
255     if not tags:
256         tags = {}
257     form = AudiobookForm(instance=audiobook)
258
259     user_can_publish = (
260         request.user.is_authenticated and
261         request.user.oauthconnection_set.filter(access=True).exists())
262
263     return render(request, "archive/file_managed.html", locals())
264
265
266 def list_unmanaged(request):
267     division = 'unmanaged'
268
269     objects = sorted(all_files(settings.UNMANAGED_PATH))
270     return render(request, "archive/list_unmanaged.html", locals())
271
272
273 def file_unmanaged(request, filename):
274     division = 'unmanaged'
275
276     tags = mutagen.File(os.path.join(settings.UNMANAGED_PATH, filename.encode('utf-8')))
277     if not tags:
278         tags = {}
279     
280     err_exists = request.GET.get('exists')
281     return render(request, "archive/file_unmanaged.html", locals())