Commit | Line | Data |
---|---|---|
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 | 17 | import logging |
095fbdaf | 18 | import os |
64da09e8 | 19 | |
eace050a | 20 | from mediagoblin.db.util import atomic_update |
4a477e24 | 21 | from mediagoblin import mg_globals as mgg |
8e5f9746 | 22 | |
6506b1e2 | 23 | from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ |
8e5f9746 | 24 | |
64da09e8 | 25 | _log = logging.getLogger(__name__) |
41f446f4 | 26 | |
41f446f4 | 27 | |
64712915 JW |
28 | class 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 | 38 | def 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 | 45 | class 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 |
77 | class 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 | 119 | def 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 |
155 | class 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 | ||
175 | class 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.') |