server/sources/storages.py
changeset 4322 f65743cc53e4
child 4329 815e08c53548
equal deleted inserted replaced
4321:80b455066c9a 4322:f65743cc53e4
       
     1 """custom storages for the system source"""
       
     2 from os import unlink, path as osp
       
     3 
       
     4 from cubicweb.server.hook import Operation
       
     5 
       
     6 
       
     7 ETYPE_ATTR_STORAGE = {}
       
     8 def set_attribute_storage(repo, etype, attr, storage):
       
     9     ETYPE_ATTR_STORAGE.setdefault(etype, {})[attr] = storage
       
    10     repo.system_source.map_attribute(etype, attr, storage.sqlgen_callback)
       
    11 
       
    12 
       
    13 class Storage(object):
       
    14     """abstract storage"""
       
    15     def sqlgen_callback(self, generator, relation, linkedvar):
       
    16         """sql generator callback when some attribute with a custom storage is
       
    17         accessed
       
    18         """
       
    19         raise NotImplementedError()
       
    20 
       
    21     def entity_added(self, entity, attr):
       
    22         """an entity using this storage for attr has been added"""
       
    23         raise NotImplementedError()
       
    24     def entity_updated(self, entity, attr):
       
    25         """an entity using this storage for attr has been updatded"""
       
    26         raise NotImplementedError()
       
    27     def entity_deleted(self, entity, attr):
       
    28         """an entity using this storage for attr has been deleted"""
       
    29         raise NotImplementedError()
       
    30 
       
    31 # TODO
       
    32 # * make it configurable without code
       
    33 # * better file path attribution
       
    34 
       
    35 class BytesFileSystemStorage(Storage):
       
    36     """store Bytes attribute value on the file system"""
       
    37     def __init__(self, defaultdir):
       
    38         self.default_directory = defaultdir
       
    39 
       
    40     def sqlgen_callback(self, generator, linkedvar, relation):
       
    41         """sql generator callback when some attribute with a custom storage is
       
    42         accessed
       
    43         """
       
    44         linkedvar.accept(generator)
       
    45         return '_fsopen(%s.cw_%s)' % (
       
    46             linkedvar._q_sql.split('.', 1)[0], # table name
       
    47             relation.r_type) # attribute name
       
    48 
       
    49     def entity_added(self, entity, attr):
       
    50         """an entity using this storage for attr has been added"""
       
    51         if not entity._cw.transaction_data.get('fs_importing'):
       
    52             try:
       
    53                 value = entity.pop(attr)
       
    54             except KeyError:
       
    55                 pass
       
    56             else:
       
    57                 fpath = self.new_fs_path(entity, attr)
       
    58                 # bytes storage used to store file's path
       
    59                 entity[attr]= Binary(fpath)
       
    60                 file(fpath, 'w').write(value.getvalue())
       
    61                 AddFileOp(entity._cw, filepath=fpath)
       
    62         # else entity[attr] is expected to be an already existant file path
       
    63 
       
    64     def entity_updated(self, entity, attr):
       
    65         """an entity using this storage for attr has been updatded"""
       
    66         try:
       
    67             value = entity.pop(attr)
       
    68         except KeyError:
       
    69             pass
       
    70         else:
       
    71             fpath = self.current_fs_path(entity, attr)
       
    72             UpdateFileOp(entity._cw, filepath=fpath, filedata=value.getvalue())
       
    73 
       
    74     def entity_deleted(self, entity, attr):
       
    75         """an entity using this storage for attr has been deleted"""
       
    76         DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
       
    77 
       
    78     def new_fs_path(self, entity, attr):
       
    79         fpath = osp.join(self.default_directory, '%s_%s_%s' % (
       
    80             self.default_directory, entity.eid, attr))
       
    81         while osp.exists(fspath):
       
    82             fspath = '_' + fspath
       
    83         return fspath
       
    84 
       
    85     def current_fs_path(self, entity, attr):
       
    86         cu = entity._cw.system_sql('SELECT cw_%s.%s WHERE cw_eid=%s' %
       
    87                                    (entity.__regid__, attr, entity.eid))
       
    88         return cu.fetchone()[0]
       
    89 
       
    90 
       
    91 class AddFileOp(Operation):
       
    92     def rollback_event(self):
       
    93         try:
       
    94             unlink(self.filepath)
       
    95         except:
       
    96             pass
       
    97 
       
    98 class DeleteFileOp(Operation):
       
    99     def commit_event(self):
       
   100         try:
       
   101             unlink(self.filepath)
       
   102         except:
       
   103             pass
       
   104 
       
   105 class UpdateFileOp(Operation):
       
   106     def precommit_event(self):
       
   107         try:
       
   108             file(self.filepath, 'w').write(self.filedata)
       
   109         except:
       
   110             pass