Convenience functions for callable hooks
[mediagoblin.git] / mediagoblin / processing / __init__.py
CommitLineData
41f446f4 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
41f446f4
CAW
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
64da09e8 17import logging
095fbdaf 18import os
64da09e8 19
eace050a 20from mediagoblin.db.util import atomic_update
4a477e24 21from mediagoblin import mg_globals as mgg
8e5f9746 22
6506b1e2 23from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
8e5f9746 24
64da09e8 25_log = logging.getLogger(__name__)
41f446f4 26
41f446f4 27
64712915
JW
28class ProgressCallback(object):
29 def __init__(self, entry):
30 self.entry = entry
31
32 def __call__(self, progress):
33 if progress:
34 self.entry.transcoding_progress = progress
35 self.entry.save()
36
37
180bdbde 38def create_pub_filepath(entry, filename):
48a7ba1e 39 return mgg.public_store.get_unique_filepath(
180bdbde 40 ['media_entries',
5c2b8486 41 unicode(entry.id),
180bdbde
E
42 filename])
43
64712915 44
28f364bd 45class FilenameBuilder(object):
4774cfa3
BS
46 """Easily slice and dice filenames.
47
28f364bd 48 Initialize this class with an original file path, then use the fill()
4774cfa3
BS
49 method to create new filenames based on the original.
50
51 """
52 MAX_FILENAME_LENGTH = 255 # VFAT's maximum filename length
095fbdaf
BS
53
54 def __init__(self, path):
28f364bd 55 """Initialize a builder from an original file path."""
095fbdaf
BS
56 self.dirpath, self.basename = os.path.split(path)
57 self.basename, self.ext = os.path.splitext(self.basename)
58 self.ext = self.ext.lower()
59
28f364bd
BS
60 def fill(self, fmtstr):
61 """Build a new filename based on the original.
4774cfa3 62
28f364bd
BS
63 The fmtstr argument can include the following:
64 {basename} -- the original basename, with the extension removed
65 {ext} -- the original extension, always lowercase
66
67 If necessary, {basename} will be truncated so the filename does not
68 exceed this class' MAX_FILENAME_LENGTH in length.
4774cfa3
BS
69
70 """
095fbdaf
BS
71 basename_len = (self.MAX_FILENAME_LENGTH -
72 len(fmtstr.format(basename='', ext=self.ext)))
73 return fmtstr.format(basename=self.basename[:basename_len],
74 ext=self.ext)
75
76
93b14fc3
E
77class ProcessingState(object):
78 def __init__(self, entry):
79 self.entry = entry
80 self.workbench = None
81 self.queued_filename = None
82
93b14fc3
E
83 def set_workbench(self, wb):
84 self.workbench = wb
85
86 def get_queued_filename(self):
87 """
88 Get the a filename for the original, on local storage
89 """
90 if self.queued_filename is not None:
91 return self.queued_filename
92 queued_filepath = self.entry.queued_media_file
93 queued_filename = self.workbench.localized_file(
94 mgg.queue_store, queued_filepath,
95 'source')
96 self.queued_filename = queued_filename
97 return queued_filename
98
715ea495 99 def copy_original(self, target_name, keyname=u"original"):
d9f61cf7
E
100 self.store_public(keyname, self.get_queued_filename(), target_name)
101
102 def store_public(self, keyname, local_file, target_name=None):
103 if target_name is None:
104 target_name = os.path.basename(local_file)
715ea495 105 target_filepath = create_pub_filepath(self.entry, target_name)
d9f61cf7
E
106 if keyname in self.entry.media_files:
107 _log.warn("store_public: keyname %r already used for file %r, "
108 "replacing with %r", keyname,
109 self.entry.media_files[keyname], target_filepath)
110 mgg.public_store.copy_local_to_storage(local_file, target_filepath)
715ea495
E
111 self.entry.media_files[keyname] = target_filepath
112
93b14fc3
E
113 def delete_queue_file(self):
114 queued_filepath = self.entry.queued_media_file
115 mgg.queue_store.delete_file(queued_filepath)
116 self.entry.queued_media_file = []
117
118
6788b412 119def mark_entry_failed(entry_id, exc):
2e5ea6b9
CAW
120 """
121 Mark a media entry as having failed in its conversion.
122
243c3843
NY
123 Uses the exception that was raised to mark more information. If
124 the exception is a derivative of BaseProcessingFail then we can
125 store extra information that can be useful for users telling them
126 why their media failed to process.
2e5ea6b9
CAW
127
128 Args:
129 - entry_id: The id of the media entry
130
131 """
6788b412
CAW
132 # Was this a BaseProcessingFail? In other words, was this a
133 # type of error that we know how to handle?
134 if isinstance(exc, BaseProcessingFail):
135 # Looks like yes, so record information about that failure and any
136 # metadata the user might have supplied.
82cd9683 137 atomic_update(mgg.database.MediaEntry,
5c2b8486 138 {'id': entry_id},
82cd9683 139 {u'state': u'failed',
5bd0adeb 140 u'fail_error': unicode(exc.exception_path),
82cd9683 141 u'fail_metadata': exc.metadata})
6788b412 142 else:
baae1578 143 _log.warn("No idea what happened here, but it failed: %r", exc)
6788b412
CAW
144 # Looks like no, so just mark it as failed and don't record a
145 # failure_error (we'll assume it wasn't handled) and don't record
146 # metadata (in fact overwrite it if somehow it had previous info
147 # here)
82cd9683 148 atomic_update(mgg.database.MediaEntry,
5c2b8486 149 {'id': entry_id},
82cd9683
E
150 {u'state': u'failed',
151 u'fail_error': None,
152 u'fail_metadata': {}})
4a477e24
CAW
153
154
8e5f9746
JW
155class BaseProcessingFail(Exception):
156 """
157 Base exception that all other processing failure messages should
158 subclass from.
159
160 You shouldn't call this itself; instead you should subclass it
161 and provid the exception_path and general_message applicable to
162 this error.
163 """
164 general_message = u''
165
166 @property
167 def exception_path(self):
168 return u"%s:%s" % (
169 self.__class__.__module__, self.__class__.__name__)
170
171 def __init__(self, **metadata):
172 self.metadata = metadata or {}
173
174
175class BadMediaFail(BaseProcessingFail):
4a477e24 176 """
8e5f9746
JW
177 Error that should be raised when an inappropriate file was given
178 for the media type specified.
4a477e24 179 """
8e5f9746 180 general_message = _(u'Invalid file given for media type.')