cubicweb/server/sources/storages.py
changeset 12828 dadbd4148a44
parent 12567 26744ad37953
equal deleted inserted replaced
12827:5d1568572895 12828:dadbd4148a44
    16 # You should have received a copy of the GNU Lesser General Public License along
    16 # You should have received a copy of the GNU Lesser General Public License along
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """custom storages for the system source"""
    18 """custom storages for the system source"""
    19 
    19 
    20 import os
    20 import os
    21 import sys
       
    22 from os import unlink, path as osp
    21 from os import unlink, path as osp
    23 from contextlib import contextmanager
    22 from contextlib import contextmanager
    24 import tempfile
    23 import tempfile
    25 
    24 
    26 from logilab.common import nullobject
    25 from logilab.common import nullobject
    32 from cubicweb.server.edition import EditedEntity
    31 from cubicweb.server.edition import EditedEntity
    33 
    32 
    34 
    33 
    35 def set_attribute_storage(repo, etype, attr, storage):
    34 def set_attribute_storage(repo, etype, attr, storage):
    36     repo.system_source.set_storage(etype, attr, storage)
    35     repo.system_source.set_storage(etype, attr, storage)
       
    36 
    37 
    37 
    38 def unset_attribute_storage(repo, etype, attr):
    38 def unset_attribute_storage(repo, etype, attr):
    39     repo.system_source.unset_storage(etype, attr)
    39     repo.system_source.unset_storage(etype, attr)
    40 
    40 
    41 
    41 
    69         raise NotImplementedError()
    69         raise NotImplementedError()
    70 
    70 
    71     def entity_added(self, entity, attr):
    71     def entity_added(self, entity, attr):
    72         """an entity using this storage for attr has been added"""
    72         """an entity using this storage for attr has been added"""
    73         raise NotImplementedError()
    73         raise NotImplementedError()
       
    74 
    74     def entity_updated(self, entity, attr):
    75     def entity_updated(self, entity, attr):
    75         """an entity using this storage for attr has been updatded"""
    76         """an entity using this storage for attr has been updatded"""
    76         raise NotImplementedError()
    77         raise NotImplementedError()
       
    78 
    77     def entity_deleted(self, entity, attr):
    79     def entity_deleted(self, entity, attr):
    78         """an entity using this storage for attr has been deleted"""
    80         """an entity using this storage for attr has been deleted"""
    79         raise NotImplementedError()
    81         raise NotImplementedError()
       
    82 
    80     def migrate_entity(self, entity, attribute):
    83     def migrate_entity(self, entity, attribute):
    81         """migrate an entity attribute to the storage"""
    84         """migrate an entity attribute to the storage"""
    82         raise NotImplementedError()
    85         raise NotImplementedError()
    83 
    86 
    84 # TODO
    87 # TODO
    85 # * make it configurable without code
    88 # * make it configurable without code
    86 # * better file path attribution
    89 # * better file path attribution
    87 # * handle backup/restore
    90 # * handle backup/restore
       
    91 
    88 
    92 
    89 def uniquify_path(dirpath, basename):
    93 def uniquify_path(dirpath, basename):
    90     """return a file descriptor and unique file name for `basename` in `dirpath`
    94     """return a file descriptor and unique file name for `basename` in `dirpath`
    91     """
    95     """
    92     path = basename.replace(osp.sep, '-')
    96     path = basename.replace(osp.sep, '-')
    93     base, ext = osp.splitext(path)
    97     base, ext = osp.splitext(path)
    94     return tempfile.mkstemp(prefix=base, suffix=ext, dir=dirpath)
    98     return tempfile.mkstemp(prefix=base, suffix=ext, dir=dirpath)
       
    99 
    95 
   100 
    96 @contextmanager
   101 @contextmanager
    97 def fsimport(cnx):
   102 def fsimport(cnx):
    98     present = 'fs_importing' in cnx.transaction_data
   103     present = 'fs_importing' in cnx.transaction_data
    99     old_value = cnx.transaction_data.get('fs_importing')
   104     old_value = cnx.transaction_data.get('fs_importing')
   126         """
   131         """
   127         os.fchmod(fd, self._wmode)
   132         os.fchmod(fd, self._wmode)
   128         fileobj = os.fdopen(fd, 'wb')
   133         fileobj = os.fdopen(fd, 'wb')
   129         binary.to_file(fileobj)
   134         binary.to_file(fileobj)
   130         fileobj.close()
   135         fileobj.close()
   131 
       
   132 
   136 
   133     def callback(self, source, cnx, value):
   137     def callback(self, source, cnx, value):
   134         """sql generator callback when some attribute with a custom storage is
   138         """sql generator callback when some attribute with a custom storage is
   135         accessed
   139         accessed
   136         """
   140         """
   218         basename = [str(entity.eid), attr]
   222         basename = [str(entity.eid), attr]
   219         name = entity.cw_attr_metadata(attr, 'name')
   223         name = entity.cw_attr_metadata(attr, 'name')
   220         if name is not None:
   224         if name is not None:
   221             basename.append(name)
   225             basename.append(name)
   222         fd, fspath = uniquify_path(self.default_directory,
   226         fd, fspath = uniquify_path(self.default_directory,
   223                                '_'.join(basename))
   227                                    '_'.join(basename))
   224         if fspath is None:
   228         if fspath is None:
   225             msg = entity._cw._('failed to uniquify path (%s, %s)') % (
   229             msg = entity._cw._('failed to uniquify path (%s, %s)') % (
   226                 self.default_directory, '_'.join(basename))
   230                 self.default_directory, '_'.join(basename))
   227             raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
   231             raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
   228         assert isinstance(fspath, str)
   232         assert isinstance(fspath, str)
   233         not stored yet.
   237         not stored yet.
   234         """
   238         """
   235         sysource = entity._cw.repo.system_source
   239         sysource = entity._cw.repo.system_source
   236         cu = sysource.doexec(entity._cw,
   240         cu = sysource.doexec(entity._cw,
   237                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
   241                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
   238                              attr, entity.cw_etype, entity.eid))
   242                                  attr, entity.cw_etype, entity.eid))
   239         rawvalue = cu.fetchone()[0]
   243         rawvalue = cu.fetchone()[0]
   240         if rawvalue is None: # no previous value
   244         if rawvalue is None:  # no previous value
   241             return None
   245             return None
   242         fspath = sysource._process_value(rawvalue, cu.description[0],
   246         fspath = sysource._process_value(rawvalue, cu.description[0],
   243                                          binarywrap=bytes)
   247                                          binarywrap=bytes)
   244         return fspath.decode('utf-8')
   248         return fspath.decode('utf-8')
   245 
   249 
   264             try:
   268             try:
   265                 unlink(filepath)
   269                 unlink(filepath)
   266             except Exception as ex:
   270             except Exception as ex:
   267                 self.error("can't remove %s: %s" % (filepath, ex))
   271                 self.error("can't remove %s: %s" % (filepath, ex))
   268 
   272 
       
   273 
   269 class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
   274 class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
   270     def postcommit_event(self):
   275     def postcommit_event(self):
   271         for filepath in self.get_data():
   276         for filepath in self.get_data():
   272             assert isinstance(filepath, str)
   277             assert isinstance(filepath, str)
   273             try:
   278             try: