PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/storages/backends/mosso.py

https://bitbucket.org/dstegelman/django-storages
Python | 347 lines | 302 code | 8 blank | 37 comment | 8 complexity | 0426e6c8bd35d2ab09bb14ac3b8f3d77 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. Custom storage for django with Mosso Cloud Files backend.
  3. Created by Rich Leland <rich@richleland.com>.
  4. """
  5. import os
  6. import warnings
  7. warnings.simplefilter('always', PendingDeprecationWarning)
  8. warnings.warn("The mosso module will be deprecated in version 1.2 of "
  9. "django-storages. The CloudFiles code has been moved into"
  10. "django-cumulus at http://github.com/richleland/django-cumulus.",
  11. PendingDeprecationWarning)
  12. from django.conf import settings
  13. from django.core.exceptions import ImproperlyConfigured
  14. from django.core.files import File
  15. from django.core.files.storage import Storage
  16. from django.utils.text import get_valid_filename
  17. try:
  18. from cStringIO import StringIO
  19. except:
  20. from StringIO import StringIO
  21. try:
  22. import cloudfiles
  23. from cloudfiles.errors import NoSuchObject
  24. except ImportError:
  25. raise ImproperlyConfigured("Could not load cloudfiles dependency. See "
  26. "http://www.mosso.com/cloudfiles.jsp.")
  27. # TODO: implement TTL into cloudfiles methods
  28. TTL = getattr(settings, 'CLOUDFILES_TTL', 600)
  29. CONNECTION_KWARGS = getattr(settings, 'CLOUDFILES_CONNECTION_KWARGS', {})
  30. SSL = getattr(settings, 'CLOUDFILES_SSL', False)
  31. def cloudfiles_upload_to(self, filename):
  32. """
  33. Simple, custom upload_to because Cloud Files doesn't support
  34. nested containers (directories).
  35. Actually found this out from @minter:
  36. @richleland The Cloud Files APIs do support pseudo-subdirectories, by
  37. creating zero-byte files with type application/directory.
  38. May implement in a future version.
  39. """
  40. return get_valid_filename(filename)
  41. class CloudFilesStorage(Storage):
  42. """
  43. Custom storage for Mosso Cloud Files.
  44. """
  45. default_quick_listdir = True
  46. def __init__(self,
  47. username=settings.CLOUDFILES_USERNAME,
  48. api_key=settings.CLOUDFILES_API_KEY,
  49. container=settings.CLOUDFILES_CONTAINER,
  50. connection_kwargs=CONNECTION_KWARGS):
  51. """
  52. Initialize the settings for the connection and container.
  53. """
  54. self.username = username
  55. self.api_key = api_key
  56. self.container_name = container
  57. self.connection_kwargs = connection_kwargs
  58. def __getstate__(self):
  59. """
  60. Return a picklable representation of the storage.
  61. """
  62. return dict(username=self.username,
  63. api_key=self.api_key,
  64. container_name=self.container_name,
  65. connection_kwargs=self.connection_kwargs)
  66. def _get_connection(self):
  67. if not hasattr(self, '_connection'):
  68. self._connection = cloudfiles.get_connection(self.username,
  69. self.api_key, **self.connection_kwargs)
  70. return self._connection
  71. def _set_connection(self, value):
  72. self._connection = value
  73. connection = property(_get_connection, _set_connection)
  74. def _get_container(self):
  75. if not hasattr(self, '_container'):
  76. self.container = self.connection.get_container(
  77. self.container_name)
  78. return self._container
  79. def _set_container(self, container):
  80. """
  81. Set the container, making it publicly available (on Limelight CDN) if
  82. it is not already.
  83. """
  84. if not container.is_public():
  85. container.make_public()
  86. if hasattr(self, '_container_public_uri'):
  87. delattr(self, '_container_public_uri')
  88. self._container = container
  89. container = property(_get_container, _set_container)
  90. def _get_container_url(self):
  91. if not hasattr(self, '_container_public_uri'):
  92. if SSL:
  93. self._container_public_uri = self.container.public_ssl_uri()
  94. else:
  95. self._container_public_uri = self.container.public_uri()
  96. return self._container_public_uri
  97. container_url = property(_get_container_url)
  98. def _get_cloud_obj(self, name):
  99. """
  100. Helper function to get retrieve the requested Cloud Files Object.
  101. """
  102. return self.container.get_object(name)
  103. def _open(self, name, mode='rb'):
  104. """
  105. Return the CloudFilesStorageFile.
  106. """
  107. return CloudFilesStorageFile(storage=self, name=name)
  108. def _save(self, name, content):
  109. """
  110. Use the Cloud Files service to write ``content`` to a remote file
  111. (called ``name``).
  112. """
  113. (path, last) = os.path.split(name)
  114. if path:
  115. try:
  116. self.container.get_object(path)
  117. except NoSuchObject:
  118. self._save(path, CloudStorageDirectory(path))
  119. cloud_obj = self.container.create_object(name)
  120. cloud_obj.size = content.size
  121. content.open()
  122. # If the content type is available, pass it in directly rather than
  123. # getting the cloud object to try to guess.
  124. if hasattr(content.file, 'content_type'):
  125. cloud_obj.content_type = content.file.content_type
  126. cloud_obj.send(content)
  127. content.close()
  128. return name
  129. def delete(self, name):
  130. """
  131. Deletes the specified file from the storage system.
  132. """
  133. # If the file exists, delete it.
  134. if self.exists(name):
  135. self.container.delete_object(name)
  136. def exists(self, name):
  137. """
  138. Returns True if a file referenced by the given name already exists in
  139. the storage system, or False if the name is available for a new file.
  140. """
  141. try:
  142. self._get_cloud_obj(name)
  143. return True
  144. except NoSuchObject:
  145. return False
  146. def listdir(self, path):
  147. """
  148. Lists the contents of the specified path, returning a 2-tuple; the
  149. first being an empty list of directories (not available for quick-
  150. listing), the second being a list of filenames.
  151. If the list of directories is required, use the full_listdir method.
  152. """
  153. files = []
  154. if path and not path.endswith('/'):
  155. path = '%s/' % path
  156. path_len = len(path)
  157. for name in self.container.list_objects(path=path):
  158. files.append(name[path_len:])
  159. return ([], files)
  160. def full_listdir(self, path):
  161. """
  162. Lists the contents of the specified path, returning a 2-tuple of lists;
  163. the first item being directories, the second item being files.
  164. On large containers, this may be a slow operation for root containers
  165. because every single object must be returned (cloudfiles does not
  166. provide an explicit way of listing directories).
  167. """
  168. dirs = set()
  169. files = []
  170. if path and not path.endswith('/'):
  171. path = '%s/' % path
  172. path_len = len(path)
  173. for name in self.container.list_objects(prefix=path):
  174. name = name[path_len:]
  175. slash = name[1:-1].find('/') + 1
  176. if slash:
  177. dirs.add(name[:slash])
  178. elif name:
  179. files.append(name)
  180. dirs = list(dirs)
  181. dirs.sort()
  182. return (dirs, files)
  183. def size(self, name):
  184. """
  185. Returns the total size, in bytes, of the file specified by name.
  186. """
  187. return self._get_cloud_obj(name).size
  188. def url(self, name):
  189. """
  190. Returns an absolute URL where the file's contents can be accessed
  191. directly by a web browser.
  192. """
  193. return '%s/%s' % (self.container_url, name)
  194. class CloudStorageDirectory(File):
  195. """
  196. A File-like object that creates a directory at cloudfiles
  197. """
  198. def __init__(self, name):
  199. super(CloudStorageDirectory, self).__init__(StringIO(), name=name)
  200. self.file.content_type = 'application/directory'
  201. self.size = 0
  202. def __str__(self):
  203. return 'directory'
  204. def __nonzero__(self):
  205. return True
  206. def open(self, mode=None):
  207. self.seek(0)
  208. def close(self):
  209. pass
  210. class CloudFilesStorageFile(File):
  211. closed = False
  212. def __init__(self, storage, name, *args, **kwargs):
  213. self._storage = storage
  214. super(CloudFilesStorageFile, self).__init__(file=None, name=name,
  215. *args, **kwargs)
  216. self._pos = 0
  217. def _get_size(self):
  218. if not hasattr(self, '_size'):
  219. self._size = self._storage.size(self.name)
  220. return self._size
  221. def _set_size(self, size):
  222. self._size = size
  223. size = property(_get_size, _set_size)
  224. def _get_file(self):
  225. if not hasattr(self, '_file'):
  226. self._file = self._storage._get_cloud_obj(self.name)
  227. return self._file
  228. def _set_file(self, value):
  229. if value is None:
  230. if hasattr(self, '_file'):
  231. del self._file
  232. else:
  233. self._file = value
  234. file = property(_get_file, _set_file)
  235. def read(self, num_bytes=None):
  236. if self._pos == self._get_size():
  237. return None
  238. if self._pos + num_bytes > self._get_size():
  239. num_bytes = self._get_size() - self._pos
  240. data = self.file.read(size=num_bytes or -1, offset=self._pos)
  241. self._pos += len(data)
  242. return data
  243. def open(self, *args, **kwargs):
  244. """
  245. Open the cloud file object.
  246. """
  247. self.file
  248. self._pos = 0
  249. def close(self, *args, **kwargs):
  250. self._pos = 0
  251. @property
  252. def closed(self):
  253. return not hasattr(self, '_file')
  254. def seek(self, pos):
  255. self._pos = pos
  256. class ThreadSafeCloudFilesStorage(CloudFilesStorage):
  257. """
  258. Extends CloudFilesStorage to make it thread safer.
  259. As long as you don't pass container or cloud objects
  260. between threads, you'll be thread safe.
  261. Uses one cloudfiles connection per thread.
  262. """
  263. def __init__(self, *args, **kwargs):
  264. super(ThreadSafeCloudFilesStorage, self).__init__(*args, **kwargs)
  265. import threading
  266. self.local_cache = threading.local()
  267. def _get_connection(self):
  268. if not hasattr(self.local_cache, 'connection'):
  269. connection = cloudfiles.get_connection(self.username,
  270. self.api_key, **self.connection_kwargs)
  271. self.local_cache.connection = connection
  272. return self.local_cache.connection
  273. connection = property(_get_connection, CloudFilesStorage._set_connection)
  274. def _get_container(self):
  275. if not hasattr(self.local_cache, 'container'):
  276. container = self.connection.get_container(self.container_name)
  277. self.local_cache.container = container
  278. return self.local_cache.container
  279. container = property(_get_container, CloudFilesStorage._set_container)