Commit | Line | Data |
---|---|---|
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 | ''' | |
18 | Make it so that ``import cloudfiles`` does not pick THIS file, but the | |
19 | python-cloudfiles one. | |
20 | ||
21 | http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports | |
22 | ''' | |
23 | from __future__ import absolute_import | |
24 | ||
25 | from mediagoblin.storage import StorageInterface, clean_listy_filepath | |
26 | ||
27 | import cloudfiles | |
28 | import mimetypes | |
29 | ||
243c3843 | 30 | |
a2468d18 JW |
31 | class 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 | ||
116 | class 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() |