1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
18 Make it so that ``import cloudfiles`` does not pick THIS file, but the
19 python-cloudfiles one.
21 http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports
23 from __future__
import absolute_import
25 from mediagoblin
.storage
import StorageInterface
, clean_listy_filepath
31 _log
= logging
.getLogger(__name__
)
34 class CloudFilesStorage(StorageInterface
):
36 OpenStack/Rackspace Cloud's Swift/CloudFiles support
41 def __init__(self
, **kwargs
):
42 self
.param_container
= kwargs
.get('cloudfiles_container')
43 self
.param_user
= kwargs
.get('cloudfiles_user')
44 self
.param_api_key
= kwargs
.get('cloudfiles_api_key')
45 self
.param_host
= kwargs
.get('cloudfiles_host')
46 self
.param_use_servicenet
= kwargs
.get('cloudfiles_use_servicenet')
48 # the Mime Type webm doesn't exists, let's add it
49 mimetypes
.add_type("video/webm", "webm")
51 if not self
.param_host
:
52 _log
.info('No CloudFiles host URL specified, '
53 'defaulting to Rackspace US')
55 self
.connection
= cloudfiles
.get_connection(
56 username
=self
.param_user
,
57 api_key
=self
.param_api_key
,
58 servicenet
=True if self
.param_use_servicenet
== 'true' or \
59 self
.param_use_servicenet
== True else False)
61 _log
.debug('Connected to {0} (auth: {1})'.format(
62 self
.connection
.connection
.host
,
63 self
.connection
.auth
.host
))
65 if not self
.param_container
== \
66 self
.connection
.get_container(self
.param_container
):
67 self
.container
= self
.connection
.create_container(
69 self
.container
.make_public(
72 self
.container
= self
.connection
.get_container(
75 _log
.debug('Container: {0}'.format(
78 self
.container_uri
= self
.container
.public_uri()
80 def _resolve_filepath(self
, filepath
):
82 clean_listy_filepath(filepath
))
84 def file_exists(self
, filepath
):
86 self
.container
.get_object(self
._resolve
_filepath
(filepath
))
88 except cloudfiles
.errors
.NoSuchObject
:
91 def get_file(self
, filepath
, *args
, **kwargs
):
93 - Doesn't care about the "mode" argument.
96 obj
= self
.container
.get_object(
97 self
._resolve
_filepath
(filepath
))
98 except cloudfiles
.errors
.NoSuchObject
:
99 obj
= self
.container
.create_object(
100 self
._resolve
_filepath
(filepath
))
102 # Detect the mimetype ourselves, since some extensions (webm)
103 # may not be universally accepted as video/webm
104 mimetype
= mimetypes
.guess_type(
108 # Set the mimetype on the CloudFiles object
109 obj
.content_type
= mimetype
[0]
110 obj
.metadata
= {'mime-type': mimetype
[0]}
112 obj
.content_type
= 'application/octet-stream'
113 obj
.metadata
= {'mime-type': 'application/octet-stream'}
115 return CloudFilesStorageObjectWrapper(obj
, *args
, **kwargs
)
117 def delete_file(self
, filepath
):
118 # TODO: Also delete unused directories if empty (safely, with
119 # checks to avoid race conditions).
121 self
.container
.delete_object(
122 self
._resolve
_filepath
(filepath
))
123 except cloudfiles
.container
.ResponseError
:
128 def file_url(self
, filepath
):
131 self
._resolve
_filepath
(filepath
)])
134 class CloudFilesStorageObjectWrapper():
136 Wrapper for python-cloudfiles's cloudfiles.storage_object.Object
137 used to circumvent the mystic `medium.jpg` corruption issue, where
138 we had both python-cloudfiles and PIL doing buffering on both
139 ends and causing breakage.
141 This wrapper currently meets mediagoblin's needs for a public_store
144 def __init__(self
, storage_object
, *args
, **kwargs
):
145 self
.storage_object
= storage_object
147 def read(self
, *args
, **kwargs
):
148 _log
.debug('Reading {0}'.format(
149 self
.storage_object
.name
))
150 return self
.storage_object
.read(*args
, **kwargs
)
152 def write(self
, data
, *args
, **kwargs
):
154 write data to the cloudfiles storage object
156 The original motivation for this wrapper is to ensure
157 that buffered writing to a cloudfiles storage object does not overwrite
158 any preexisting data.
160 Currently this method does not support any write modes except "append".
161 However if we should need it it would be easy implement.
163 if self
.storage_object
.size
and type(data
) == str:
164 _log
.debug('{0} is > 0 in size, appending data'.format(
165 self
.storage_object
.name
))
166 data
= self
.read() + data
168 _log
.debug('Writing {0}'.format(
169 self
.storage_object
.name
))
170 self
.storage_object
.write(data
, *args
, **kwargs
)
180 Context Manager API implementation
181 http://docs.python.org/library/stdtypes.html#context-manager-types
185 def __exit__(self
, *exc_info
):
187 Context Manger API implementation