Merge branch '512_bump_video_js'
[mediagoblin.git] / mediagoblin / plugins / api / views.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import json
18 import logging
19 import uuid
20
21 from os.path import splitext
22 from werkzeug.datastructures import FileStorage
23 from werkzeug.exceptions import BadRequest, Forbidden
24 from werkzeug.utils import secure_filename
25 from werkzeug.wrappers import Response
26 from celery import registry
27
28 from mediagoblin.decorators import require_active_login
29 from mediagoblin.processing import mark_entry_failed
30 from mediagoblin.processing.task import ProcessMedia
31 from mediagoblin.meddleware.csrf import csrf_exempt
32 from mediagoblin.media_types import sniff_media
33 from mediagoblin.plugins.api.tools import api_auth, get_entry_serializable, \
34 json_response
35
36 _log = logging.getLogger(__name__)
37
38
39 @csrf_exempt
40 @api_auth
41 @require_active_login
42 def post_entry(request):
43 _log.debug('Posting entry')
44
45 if request.method == 'OPTIONS':
46 return json_response({'status': 200})
47
48 if request.method != 'POST':
49 _log.debug('Must POST against post_entry')
50 raise BadRequest()
51
52 if not 'file' in request.files \
53 or not isinstance(request.files['file'], FileStorage) \
54 or not request.files['file'].stream:
55 _log.debug('File field not found')
56 raise BadRequest()
57
58 media_file = request.files['file']
59
60 media_type, media_manager = sniff_media(media_file)
61
62 entry = request.db.MediaEntry()
63 entry.media_type = unicode(media_type)
64 entry.title = unicode(request.form.get('title')
65 or splitext(media_file.filename)[0])
66
67 entry.description = unicode(request.form.get('description'))
68 entry.license = unicode(request.form.get('license', ''))
69
70 entry.uploader = request.user.id
71
72 entry.generate_slug()
73
74 task_id = unicode(uuid.uuid4())
75
76 # Now store generate the queueing related filename
77 queue_filepath = request.app.queue_store.get_unique_filepath(
78 ['media_entries',
79 task_id,
80 secure_filename(media_file.filename)])
81
82 # queue appropriately
83 queue_file = request.app.queue_store.get_file(
84 queue_filepath, 'wb')
85
86 with queue_file:
87 queue_file.write(request.files['file'].stream.read())
88
89 # Add queued filename to the entry
90 entry.queued_media_file = queue_filepath
91
92 entry.queued_task_id = task_id
93
94 # Save now so we have this data before kicking off processing
95 entry.save()
96
97 if request.form.get('callback_url'):
98 metadata = request.db.ProcessingMetaData()
99 metadata.media_entry = entry
100 metadata.callback_url = unicode(request.form['callback_url'])
101 metadata.save()
102
103 # Pass off to processing
104 #
105 # (... don't change entry after this point to avoid race
106 # conditions with changes to the document via processing code)
107 process_media = registry.tasks[ProcessMedia.name]
108 try:
109 process_media.apply_async(
110 [unicode(entry.id)], {},
111 task_id=task_id)
112 except BaseException as e:
113 # The purpose of this section is because when running in "lazy"
114 # or always-eager-with-exceptions-propagated celery mode that
115 # the failure handling won't happen on Celery end. Since we
116 # expect a lot of users to run things in this way we have to
117 # capture stuff here.
118 #
119 # ... not completely the diaper pattern because the
120 # exception is re-raised :)
121 mark_entry_failed(entry.id, e)
122 # re-raise the exception
123 raise
124
125 return json_response(get_entry_serializable(entry, request.urlgen))
126
127
128 @api_auth
129 def api_test(request):
130 if not request.user:
131 raise Forbidden()
132
133 user_data = {
134 'username': request.user.username,
135 'email': request.user.email}
136
137 # TODO: This is the *only* thing using Response() here, should that
138 # not simply use json_response()?
139 return Response(json.dumps(user_data))
140
141
142 def get_entries(request):
143 entries = request.db.MediaEntry.query
144
145 # TODO: Make it possible to fetch unprocessed media, or media in-processing
146 entries = entries.filter_by(state=u'processed')
147
148 # TODO: Add sort order customization
149 entries = entries.order_by(request.db.MediaEntry.created.desc())
150
151 # TODO: Fetch default and upper limit from config
152 entries = entries.limit(int(request.GET.get('limit') or 10))
153
154 entries_serializable = []
155
156 for entry in entries:
157 entries_serializable.append(get_entry_serializable(entry, request.urlgen))
158
159 return json_response(entries_serializable)