server/sources/storages.py
changeset 5238 31c12863fd9d
parent 5219 35d44017c72b
child 5396 78d92a47a4e5
child 5421 8167de96c523
equal deleted inserted replaced
5237:10dd0dd78778 5238:31c12863fd9d
     1 """custom storages for the system source"""
     1 """custom storages for the system source"""
     2 from os import unlink, path as osp
     2 from os import unlink, path as osp
       
     3 
       
     4 from yams.schema import role_name
     3 
     5 
     4 from cubicweb import Binary
     6 from cubicweb import Binary
     5 from cubicweb.server.hook import Operation
     7 from cubicweb.server.hook import Operation
     6 
     8 
     7 def set_attribute_storage(repo, etype, attr, storage):
     9 def set_attribute_storage(repo, etype, attr, storage):
    52 # TODO
    54 # TODO
    53 # * make it configurable without code
    55 # * make it configurable without code
    54 # * better file path attribution
    56 # * better file path attribution
    55 # * handle backup/restore
    57 # * handle backup/restore
    56 
    58 
       
    59 def uniquify_path(dirpath, basename):
       
    60     """return a unique file name for `basename` in `dirpath`, or None
       
    61     if all attemps failed.
       
    62 
       
    63     XXX subject to race condition.
       
    64     """
       
    65     path = osp.join(dirpath, basename)
       
    66     if not osp.isfile(path):
       
    67         return path
       
    68     base, ext = osp.splitext(path)
       
    69     for i in xrange(1, 256):
       
    70         path = '%s%s%s' % (base, i, ext)
       
    71         if not osp.isfile(path):
       
    72             return path
       
    73     return None
       
    74 
    57 class BytesFileSystemStorage(Storage):
    75 class BytesFileSystemStorage(Storage):
    58     """store Bytes attribute value on the file system"""
    76     """store Bytes attribute value on the file system"""
    59     def __init__(self, defaultdir):
    77     def __init__(self, defaultdir, fsencoding='utf-8'):
    60         self.default_directory = defaultdir
    78         self.default_directory = defaultdir
       
    79         self.fsencoding = fsencoding
    61 
    80 
    62     def callback(self, source, value):
    81     def callback(self, source, value):
    63         """sql generator callback when some attribute with a custom storage is
    82         """sql generator callback when some attribute with a custom storage is
    64         accessed
    83         accessed
    65         """
    84         """
   100     def entity_deleted(self, entity, attr):
   119     def entity_deleted(self, entity, attr):
   101         """an entity using this storage for attr has been deleted"""
   120         """an entity using this storage for attr has been deleted"""
   102         DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
   121         DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
   103 
   122 
   104     def new_fs_path(self, entity, attr):
   123     def new_fs_path(self, entity, attr):
   105         fspath = osp.join(self.default_directory, '%s_%s' % (entity.eid, attr))
   124         # We try to get some hint about how to name the file using attribute's
   106         while osp.exists(fspath):
   125         # name metadata, so we use the real file name and extension when
   107             fspath = '_' + fspath
   126         # available. Keeping the extension is useful for example in the case of
       
   127         # PIL processing that use filename extension to detect content-type, as
       
   128         # well as providing more understandable file names on the fs.
       
   129         basename = [str(entity.eid), attr]
       
   130         name = entity.attr_metadata(attr, 'name')
       
   131         if name is not None:
       
   132             basename.append(name.encode(self.fsencoding))
       
   133         fspath = uniquify_path(self.default_directory, '_'.join(basename))
       
   134         if fspath is None:
       
   135             msg = entity._cw._('failed to uniquify path (%s, %s)') % (
       
   136                 dirpath, '_'.join(basename))
       
   137             raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
   108         return fspath
   138         return fspath
   109 
   139 
   110     def current_fs_path(self, entity, attr):
   140     def current_fs_path(self, entity, attr):
   111         sysource = entity._cw.pool.source('system')
   141         sysource = entity._cw.pool.source('system')
   112         cu = sysource.doexec(entity._cw,
   142         cu = sysource.doexec(entity._cw,
   113                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
   143                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
   114                                  attr, entity.__regid__, entity.eid))
   144                              attr, entity.__regid__, entity.eid))
   115         rawvalue = cu.fetchone()[0]
   145         rawvalue = cu.fetchone()[0]
   116         if rawvalue is None: # no previous value
   146         if rawvalue is None: # no previous value
   117             return self.new_fs_path(entity, attr)
   147             return self.new_fs_path(entity, attr)
   118         return sysource._process_value(rawvalue, cu.description[0],
   148         return sysource._process_value(rawvalue, cu.description[0],
   119                                        binarywrap=str)
   149                                        binarywrap=str)