| 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 mediagoblin.mg_globals as mg_globals |
| 18 | import uuid |
| 19 | from os.path import splitext |
| 20 | from cgi import FieldStorage |
| 21 | |
| 22 | from celery import registry |
| 23 | import urllib,urllib2 |
| 24 | import logging |
| 25 | |
| 26 | _log = logging.getLogger(__name__) |
| 27 | |
| 28 | from werkzeug.utils import secure_filename |
| 29 | |
| 30 | from mediagoblin.db.util import ObjectId |
| 31 | from mediagoblin.tools.text import convert_to_tag_list_of_dicts |
| 32 | from mediagoblin.tools.translate import pass_to_ugettext as _ |
| 33 | from mediagoblin.tools.response import render_to_response, redirect |
| 34 | from mediagoblin.decorators import require_active_login |
| 35 | from mediagoblin.submit import forms as submit_forms, security |
| 36 | from mediagoblin.processing import mark_entry_failed, ProcessMedia |
| 37 | from mediagoblin.messages import add_message, SUCCESS |
| 38 | from mediagoblin.media_types import get_media_type_and_manager, \ |
| 39 | InvalidFileType, FileTypeNotSupported |
| 40 | |
| 41 | |
| 42 | @require_active_login |
| 43 | def submit_start(request): |
| 44 | """ |
| 45 | First view for submitting a file. |
| 46 | """ |
| 47 | submit_form = submit_forms.SubmitStartForm(request.POST) |
| 48 | |
| 49 | if request.method == 'POST' and submit_form.validate(): |
| 50 | if not ('file' in request.POST |
| 51 | and isinstance(request.POST['file'], FieldStorage) |
| 52 | and request.POST['file'].file): |
| 53 | submit_form.file.errors.append( |
| 54 | _(u'You must provide a file.')) |
| 55 | else: |
| 56 | try: |
| 57 | filename = request.POST['file'].filename |
| 58 | media_type, media_manager = get_media_type_and_manager(filename) |
| 59 | |
| 60 | # create entry and save in database |
| 61 | entry = request.db.MediaEntry() |
| 62 | entry.id = ObjectId() |
| 63 | entry.media_type = unicode(media_type) |
| 64 | entry.title = ( |
| 65 | unicode(request.POST['title']) |
| 66 | or unicode(splitext(filename)[0])) |
| 67 | |
| 68 | entry.description = unicode(request.POST.get('description')) |
| 69 | |
| 70 | entry.license = unicode(request.POST.get('license', "")) or None |
| 71 | |
| 72 | entry.uploader = request.user._id |
| 73 | |
| 74 | # Process the user's folksonomy "tags" |
| 75 | entry.tags = convert_to_tag_list_of_dicts( |
| 76 | request.POST.get('tags')) |
| 77 | |
| 78 | # Generate a slug from the title |
| 79 | entry.generate_slug() |
| 80 | |
| 81 | # We generate this ourselves so we know what the taks id is for |
| 82 | # retrieval later. |
| 83 | |
| 84 | # (If we got it off the task's auto-generation, there'd be |
| 85 | # a risk of a race condition when we'd save after sending |
| 86 | # off the task) |
| 87 | task_id = unicode(uuid.uuid4()) |
| 88 | |
| 89 | # Now store generate the queueing related filename |
| 90 | queue_filepath = request.app.queue_store.get_unique_filepath( |
| 91 | ['media_entries', |
| 92 | task_id, |
| 93 | secure_filename(filename)]) |
| 94 | |
| 95 | # queue appropriately |
| 96 | queue_file = request.app.queue_store.get_file( |
| 97 | queue_filepath, 'wb') |
| 98 | |
| 99 | with queue_file: |
| 100 | queue_file.write(request.POST['file'].file.read()) |
| 101 | |
| 102 | # Add queued filename to the entry |
| 103 | entry.queued_media_file = queue_filepath |
| 104 | |
| 105 | entry.queued_task_id = task_id |
| 106 | |
| 107 | # Save now so we have this data before kicking off processing |
| 108 | entry.save(validate=True) |
| 109 | |
| 110 | # Pass off to processing |
| 111 | # |
| 112 | # (... don't change entry after this point to avoid race |
| 113 | # conditions with changes to the document via processing code) |
| 114 | process_media = registry.tasks[ProcessMedia.name] |
| 115 | try: |
| 116 | process_media.apply_async( |
| 117 | [unicode(entry._id)], {}, |
| 118 | task_id=task_id) |
| 119 | except BaseException as exc: |
| 120 | # The purpose of this section is because when running in "lazy" |
| 121 | # or always-eager-with-exceptions-propagated celery mode that |
| 122 | # the failure handling won't happen on Celery end. Since we |
| 123 | # expect a lot of users to run things in this way we have to |
| 124 | # capture stuff here. |
| 125 | # |
| 126 | # ... not completely the diaper pattern because the |
| 127 | # exception is re-raised :) |
| 128 | mark_entry_failed(entry._id, exc) |
| 129 | # re-raise the exception |
| 130 | raise |
| 131 | |
| 132 | if mg_globals.app_config["push_urls"]: |
| 133 | feed_url=request.urlgen( |
| 134 | 'mediagoblin.user_pages.atom_feed', |
| 135 | qualified=True,user=request.user.username) |
| 136 | hubparameters = { |
| 137 | 'hub.mode': 'publish', |
| 138 | 'hub.url': feed_url} |
| 139 | hubdata = urllib.urlencode(hubparameters) |
| 140 | hubheaders = { |
| 141 | "Content-type": "application/x-www-form-urlencoded", |
| 142 | "Connection": "close"} |
| 143 | for huburl in mg_globals.app_config["push_urls"]: |
| 144 | hubrequest = urllib2.Request(huburl, hubdata, hubheaders) |
| 145 | try: |
| 146 | hubresponse = urllib2.urlopen(hubrequest) |
| 147 | except urllib2.HTTPError as exc: |
| 148 | # This is not a big issue, the item will be fetched |
| 149 | # by the PuSH server next time we hit it |
| 150 | _log.warning( |
| 151 | "push url %r gave error %r", huburl, exc.code) |
| 152 | except urllib2.URLError as exc: |
| 153 | _log.warning( |
| 154 | "push url %r is unreachable %r", huburl, exc.reason) |
| 155 | |
| 156 | add_message(request, SUCCESS, _('Woohoo! Submitted!')) |
| 157 | |
| 158 | return redirect(request, "mediagoblin.user_pages.user_home", |
| 159 | user=request.user.username) |
| 160 | except Exception as e: |
| 161 | ''' |
| 162 | This section is intended to catch exceptions raised in |
| 163 | mediagobling.media_types |
| 164 | ''' |
| 165 | |
| 166 | if isinstance(e, InvalidFileType) or \ |
| 167 | isinstance(e, FileTypeNotSupported): |
| 168 | submit_form.file.errors.append( |
| 169 | e) |
| 170 | else: |
| 171 | raise |
| 172 | |
| 173 | return render_to_response( |
| 174 | request, |
| 175 | 'mediagoblin/submit/start.html', |
| 176 | {'submit_form': submit_form, |
| 177 | 'app_config': mg_globals.app_config}) |