"""Custom storage for django with Mosso Cloud Files backend.Created by Rich Leland <rich@richleland.com>."""fromdjango.confimportsettingsfromdjango.core.exceptionsimportImproperlyConfiguredfromdjango.core.filesimportFilefromdjango.core.files.storageimportStoragefromdjango.utils.textimportget_valid_filenametry:importcloudfilesfromcloudfiles.errorsimportNoSuchObjectexceptImportError:raiseImproperlyConfigured("Could not load cloudfiles dependency. See ""http://www.mosso.com/cloudfiles.jsp.")# TODO: implement TTL into cloudfiles methodsCLOUDFILES_TTL=getattr(settings,'CLOUDFILES_TTL',600)defcloudfiles_upload_to(self,filename):""" Simple, custom upload_to because Cloud Files doesn't support nested containers (directories). Actually found this out from @minter: @richleland The Cloud Files APIs do support pseudo-subdirectories, by creating zero-byte files with type application/directory. May implement in a future version. """returnget_valid_filename(filename)classCloudFilesStorage(Storage):""" Custom storage for Mosso Cloud Files. """default_quick_listdir=Truedef__init__(self,username=None,api_key=None,container=None,connection_kwargs=None):""" Initialize the settings for the connection and container. """self.username=usernameorsettings.CLOUDFILES_USERNAMEself.api_key=api_keyorsettings.CLOUDFILES_API_KEYself.container_name=containerorsettings.CLOUDFILES_CONTAINERself.connection_kwargs=connection_kwargsor{}def__getstate__(self):""" Return a picklable representation of the storage. """returndict(username=self.username,api_key=self.api_key,container_name=self.container_name,connection_kwargs=self.connection_kwargs)def_get_connection(self):ifnothasattr(self,'_connection'):self._connection=cloudfiles.get_connection(self.username,self.api_key,**self.connection_kwargs)returnself._connectiondef_set_connection(self,value):self._connection=valueconnection=property(_get_connection,_set_connection)def_get_container(self):ifnothasattr(self,'_container'):self.container=self.connection.get_container(self.container_name)returnself._containerdef_set_container(self,container):""" Set the container, making it publicly available (on Limelight CDN) if it is not already. """ifnotcontainer.is_public():container.make_public()ifhasattr(self,'_container_public_uri'):delattr(self,'_container_public_uri')self._container=containercontainer=property(_get_container,_set_container)def_get_container_url(self):ifnothasattr(self,'_container_public_uri'):self._container_public_uri=self.container.public_uri()returnself._container_public_uricontainer_url=property(_get_container_url)def_get_cloud_obj(self,name):""" Helper function to get retrieve the requested Cloud Files Object. """returnself.container.get_object(name)def_open(self,name,mode='rb'):""" Return the CloudFilesStorageFile. """returnCloudFilesStorageFile(storage=self,name=name)def_save(self,name,content):""" Use the Cloud Files service to write ``content`` to a remote file (called ``name``). """cloud_obj=self.container.create_object(name)cloud_obj.size=content.sizecontent.open()# If the content type is available, pass it in directly rather than# getting the cloud object to try to guess.ifhasattr(content.file,'content_type'):cloud_obj.content_type=content.file.content_typecloud_obj.send(content)content.close()returnnamedefdelete(self,name):""" Deletes the specified file from the storage system. """# If the file exists, delete it.ifself.exists(name):self.container.delete_object(name)defexists(self,name):""" Returns True if a file referenced by the given name already exists in the storage system, or False if the name is available for a new file. """try:self._get_cloud_obj(name)returnTrueexceptNoSuchObject:returnFalsedeflistdir(self,path):""" Lists the contents of the specified path, returning a 2-tuple; the first being an empty list of directories (not available for quick- listing), the second being a list of filenames. If the list of directories is required, use the full_listdir method. """files=[]ifpathandnotpath.endswith('/'):path='%s/'%pathpath_len=len(path)fornameinself.container.list_objects(path=path):files.append(name[path_len:])return([],files)deffull_listdir(self,path):""" Lists the contents of the specified path, returning a 2-tuple of lists; the first item being directories, the second item being files. On large containers, this may be a slow operation for root containers because every single object must be returned (cloudfiles does not provide an explicit way of listing directories). """dirs=set()files=[]ifpathandnotpath.endswith('/'):path='%s/'%pathpath_len=len(path)fornameinself.container.list_objects(prefix=path):name=name[path_len:]slash=name[1:-1].find('/')+1ifslash:dirs.add(name[:slash])elifname:files.append(name)dirs=list(dirs)dirs.sort()return(dirs,files)defsize(self,name):""" Returns the total size, in bytes, of the file specified by name. """returnself._get_cloud_obj(name).sizedefurl(self,name):""" Returns an absolute URL where the file's contents can be accessed directly by a web browser. """return'%s/%s'%(self.container_url,name)classCloudFilesStorageFile(File):closed=Falsedef__init__(self,storage,name,*args,**kwargs):self._storage=storagesuper(CloudFilesStorageFile,self).__init__(file=None,name=name,*args,**kwargs)def_get_size(self):ifnothasattr(self,'_size'):self._size=self._storage.size(self.name)returnself._sizedef_set_size(self,size):self._size=sizesize=property(_get_size,_set_size)def_get_file(self):ifnothasattr(self,'_file'):self._file=self._storage._get_cloud_obj(self.name)returnself._filedef_set_file(self,value):ifvalueisNone:ifhasattr(self,'_file'):delself._fileelse:self._file=valuefile=property(_get_file,_set_file)defread(self,num_bytes=None):data=self.file.read(size=num_bytesor-1,offset=self._pos)self._pos+=len(data)returndatadefopen(self,*args,**kwargs):""" Open the cloud file object. """self.fileself._pos=0defclose(self,*args,**kwargs):self._pos=0@propertydefclosed(self):returnnothasattr(self,'_file')defseek(self,pos):self._pos=posclassThreadSafeCloudFilesStorage(CloudFilesStorage):""" Extends CloudFilesStorage to make it thread safer. As long as you don't pass container or cloud objects between threads, you'll be thread safe. Uses one cloudfiles connection per thread. """def__init__(self,*args,**kwargs):super(ThreadSafeCloudFilesStorage,self).__init__(*args,**kwargs)importthreadingself.local_cache=threading.local()def_get_connection(self):ifnothasattr(self.local_cache,'connection'):connection=cloudfiles.get_connection(self.username,self.api_key,**self.connection_kwargs)self.local_cache.connection=connectionreturnself.local_cache.connectionconnection=property(_get_connection,CloudFilesStorage._set_connection)def_get_container(self):ifnothasattr(self.local_cache,'container'):container=self.connection.get_container(self.container_name)self.local_cache.container=containerreturnself.local_cache.containercontainer=property(_get_container,CloudFilesStorage._set_container)