It's 2012 all up in here
[mediagoblin.git] / mediagoblin / storage / cloudfiles.py
CommitLineData
a2468d18 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
a2468d18
JW
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'''
18Make it so that ``import cloudfiles`` does not pick THIS file, but the
19python-cloudfiles one.
20
21http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports
22'''
23from __future__ import absolute_import
24
25from mediagoblin.storage import StorageInterface, clean_listy_filepath
26
27import cloudfiles
28import mimetypes
29
243c3843 30
a2468d18
JW
31class CloudFilesStorage(StorageInterface):
32 '''
33 OpenStack/Rackspace Cloud's Swift/CloudFiles support
34 '''
35
36 local_storage = False
37
38 def __init__(self, **kwargs):
39 self.param_container = kwargs.get('cloudfiles_container')
40 self.param_user = kwargs.get('cloudfiles_user')
41 self.param_api_key = kwargs.get('cloudfiles_api_key')
42 self.param_host = kwargs.get('cloudfiles_host')
43 self.param_use_servicenet = kwargs.get('cloudfiles_use_servicenet')
44
45 if not self.param_host:
46 print('No CloudFiles host URL specified, '
47 'defaulting to Rackspace US')
48
49 self.connection = cloudfiles.get_connection(
50 username=self.param_user,
51 api_key=self.param_api_key,
52 servicenet=True if self.param_use_servicenet == 'true' or \
53 self.param_use_servicenet == True else False)
54
55 if not self.param_container == \
56 self.connection.get_container(self.param_container):
57 self.container = self.connection.create_container(
58 self.param_container)
59 self.container.make_public(
60 ttl=60 * 60 * 2)
61 else:
62 self.container = self.connection.get_container(
63 self.param_container)
64
65 self.container_uri = self.container.public_uri()
66
67 def _resolve_filepath(self, filepath):
68 return '/'.join(
69 clean_listy_filepath(filepath))
70
71 def file_exists(self, filepath):
72 try:
73 object = self.container.get_object(
74 self._resolve_filepath(filepath))
75 return True
76 except cloudfiles.errors.NoSuchObject:
77 return False
78
79 def get_file(self, filepath, *args, **kwargs):
80 """
81 - Doesn't care about the "mode" argument
82 """
83 try:
84 obj = self.container.get_object(
85 self._resolve_filepath(filepath))
86 except cloudfiles.errors.NoSuchObject:
87 obj = self.container.create_object(
88 self._resolve_filepath(filepath))
89
90 mimetype = mimetypes.guess_type(
91 filepath[-1])
92
93 if mimetype:
94 obj.content_type = mimetype[0]
95
96 return CloudFilesStorageObjectWrapper(obj, *args, **kwargs)
97
98 def delete_file(self, filepath):
99 # TODO: Also delete unused directories if empty (safely, with
100 # checks to avoid race conditions).
93bdab9d
JW
101 try:
102 self.container.delete_object(
103 self._resolve_filepath(filepath))
104 except cloudfiles.container.ResponseError:
105 pass
106 finally:
107 pass
108
a2468d18
JW
109
110 def file_url(self, filepath):
111 return '/'.join([
112 self.container_uri,
113 self._resolve_filepath(filepath)])
114
115
116class CloudFilesStorageObjectWrapper():
117 """
118 Wrapper for python-cloudfiles's cloudfiles.storage_object.Object
119 used to circumvent the mystic `medium.jpg` corruption issue, where
120 we had both python-cloudfiles and PIL doing buffering on both
121 ends and that breaking things.
122
123 This wrapper currently meets mediagoblin's needs for a public_store
124 file-like object.
125 """
126 def __init__(self, storage_object, *args, **kwargs):
127 self.storage_object = storage_object
128
129 def read(self, *args, **kwargs):
130 return self.storage_object.read(*args, **kwargs)
131
132 def write(self, data, *args, **kwargs):
133 """
134 write data to the cloudfiles storage object
135
136 The original motivation for this wrapper is to ensure
137 that buffered writing to a cloudfiles storage object does not overwrite
138 any preexisting data.
139
140 Currently this method does not support any write modes except "append".
141 However if we should need it it would be easy implement.
142 """
143 if self.storage_object.size and type(data) == str:
144 data = self.read() + data
145
146 self.storage_object.write(data, *args, **kwargs)
147
148 def close(self):
149 pass
150
151 def __enter__(self):
152 """
153 Context Manager API implementation
154 http://docs.python.org/library/stdtypes.html#context-manager-types
155 """
156 return self
157
158 def __exit__(self, *exc_info):
159 """
160 Context Manger API implementation
161 see self.__enter__()
162 """
163 self.close()