cubicweb/server/sources/storages.py
changeset 12567 26744ad37953
parent 11273 c655e19cbc35
child 12828 dadbd4148a44
equal deleted inserted replaced
12566:6b3523f81f42 12567:26744ad37953
    20 import os
    20 import os
    21 import sys
    21 import sys
    22 from os import unlink, path as osp
    22 from os import unlink, path as osp
    23 from contextlib import contextmanager
    23 from contextlib import contextmanager
    24 import tempfile
    24 import tempfile
    25 
       
    26 from six import PY2, PY3, text_type, binary_type
       
    27 
    25 
    28 from logilab.common import nullobject
    26 from logilab.common import nullobject
    29 
    27 
    30 from yams.schema import role_name
    28 from yams.schema import role_name
    31 
    29 
   111 
   109 
   112 
   110 
   113 class BytesFileSystemStorage(Storage):
   111 class BytesFileSystemStorage(Storage):
   114     """store Bytes attribute value on the file system"""
   112     """store Bytes attribute value on the file system"""
   115     def __init__(self, defaultdir, fsencoding=_marker, wmode=0o444):
   113     def __init__(self, defaultdir, fsencoding=_marker, wmode=0o444):
   116         if PY3:
   114         if fsencoding is not _marker:
   117             if not isinstance(defaultdir, text_type):
   115             raise ValueError('fsencoding is no longer supported in python 3')
   118                 raise TypeError('defaultdir must be a unicode object in python 3')
       
   119             if fsencoding is not _marker:
       
   120                 raise ValueError('fsencoding is no longer supported in python 3')
       
   121         else:
       
   122             self.fsencoding = fsencoding or 'utf-8'
       
   123             if isinstance(defaultdir, text_type):
       
   124                 defaultdir = defaultdir.encode(fsencoding)
       
   125         self.default_directory = defaultdir
   116         self.default_directory = defaultdir
   126         # extra umask to use when creating file
   117         # extra umask to use when creating file
   127         # 0444 as in "only allow read bit in permission"
   118         # 0444 as in "only allow read bit in permission"
   128         self._wmode = wmode
   119         self._wmode = wmode
   129 
   120 
   158         else:
   149         else:
   159             binary = entity.cw_edited.pop(attr)
   150             binary = entity.cw_edited.pop(attr)
   160             if binary is not None:
   151             if binary is not None:
   161                 fd, fpath = self.new_fs_path(entity, attr)
   152                 fd, fpath = self.new_fs_path(entity, attr)
   162                 # bytes storage used to store file's path
   153                 # bytes storage used to store file's path
   163                 binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8'))
   154                 binary_obj = Binary(fpath.encode('utf-8'))
   164                 entity.cw_edited.edited_attribute(attr, binary_obj)
   155                 entity.cw_edited.edited_attribute(attr, binary_obj)
   165                 self._writecontent(fd, binary)
   156                 self._writecontent(fd, binary)
   166                 AddFileOp.get_instance(entity._cw).add_data(fpath)
   157                 AddFileOp.get_instance(entity._cw).add_data(fpath)
   167         return binary
   158         return binary
   168 
   159 
   202             # reinstall poped value
   193             # reinstall poped value
   203             if fpath is None:
   194             if fpath is None:
   204                 entity.cw_edited.edited_attribute(attr, None)
   195                 entity.cw_edited.edited_attribute(attr, None)
   205             else:
   196             else:
   206                 # register the new location for the file.
   197                 # register the new location for the file.
   207                 binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8'))
   198                 binary_obj = Binary(fpath.encode('utf-8'))
   208                 entity.cw_edited.edited_attribute(attr, binary_obj)
   199                 entity.cw_edited.edited_attribute(attr, binary_obj)
   209         if oldpath is not None and oldpath != fpath:
   200         if oldpath is not None and oldpath != fpath:
   210             # Mark the old file as useless so the file will be removed at
   201             # Mark the old file as useless so the file will be removed at
   211             # commit.
   202             # commit.
   212             DeleteFileOp.get_instance(entity._cw).add_data(oldpath)
   203             DeleteFileOp.get_instance(entity._cw).add_data(oldpath)
   222         # We try to get some hint about how to name the file using attribute's
   213         # We try to get some hint about how to name the file using attribute's
   223         # name metadata, so we use the real file name and extension when
   214         # name metadata, so we use the real file name and extension when
   224         # available. Keeping the extension is useful for example in the case of
   215         # available. Keeping the extension is useful for example in the case of
   225         # PIL processing that use filename extension to detect content-type, as
   216         # PIL processing that use filename extension to detect content-type, as
   226         # well as providing more understandable file names on the fs.
   217         # well as providing more understandable file names on the fs.
   227         if PY2:
       
   228             attr = attr.encode('ascii')
       
   229         basename = [str(entity.eid), attr]
   218         basename = [str(entity.eid), attr]
   230         name = entity.cw_attr_metadata(attr, 'name')
   219         name = entity.cw_attr_metadata(attr, 'name')
   231         if name is not None:
   220         if name is not None:
   232             basename.append(name.encode(self.fsencoding) if PY2 else name)
   221             basename.append(name)
   233         fd, fspath = uniquify_path(self.default_directory,
   222         fd, fspath = uniquify_path(self.default_directory,
   234                                '_'.join(basename))
   223                                '_'.join(basename))
   235         if fspath is None:
   224         if fspath is None:
   236             msg = entity._cw._('failed to uniquify path (%s, %s)') % (
   225             msg = entity._cw._('failed to uniquify path (%s, %s)') % (
   237                 self.default_directory, '_'.join(basename))
   226                 self.default_directory, '_'.join(basename))
   238             raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
   227             raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
   239         assert isinstance(fspath, str)  # bytes on py2, unicode on py3
   228         assert isinstance(fspath, str)
   240         return fd, fspath
   229         return fd, fspath
   241 
   230 
   242     def current_fs_path(self, entity, attr):
   231     def current_fs_path(self, entity, attr):
   243         """return the current fs_path of the attribute, or None is the attr is
   232         """return the current fs_path of the attribute, or None is the attr is
   244         not stored yet.
   233         not stored yet.
   249                              attr, entity.cw_etype, entity.eid))
   238                              attr, entity.cw_etype, entity.eid))
   250         rawvalue = cu.fetchone()[0]
   239         rawvalue = cu.fetchone()[0]
   251         if rawvalue is None: # no previous value
   240         if rawvalue is None: # no previous value
   252             return None
   241             return None
   253         fspath = sysource._process_value(rawvalue, cu.description[0],
   242         fspath = sysource._process_value(rawvalue, cu.description[0],
   254                                          binarywrap=binary_type)
   243                                          binarywrap=bytes)
   255         if PY3:
   244         return fspath.decode('utf-8')
   256             fspath = fspath.decode('utf-8')
       
   257         assert isinstance(fspath, str)  # bytes on py2, unicode on py3
       
   258         return fspath
       
   259 
   245 
   260     def migrate_entity(self, entity, attribute):
   246     def migrate_entity(self, entity, attribute):
   261         """migrate an entity attribute to the storage"""
   247         """migrate an entity attribute to the storage"""
   262         entity.cw_edited = EditedEntity(entity, **entity.cw_attr_cache)
   248         entity.cw_edited = EditedEntity(entity, **entity.cw_attr_cache)
   263         binary = self.entity_added(entity, attribute)
   249         binary = self.entity_added(entity, attribute)
   272 
   258 
   273 
   259 
   274 class AddFileOp(hook.DataOperationMixIn, hook.Operation):
   260 class AddFileOp(hook.DataOperationMixIn, hook.Operation):
   275     def rollback_event(self):
   261     def rollback_event(self):
   276         for filepath in self.get_data():
   262         for filepath in self.get_data():
   277             assert isinstance(filepath, str)  # bytes on py2, unicode on py3
   263             assert isinstance(filepath, str)
   278             try:
   264             try:
   279                 unlink(filepath)
   265                 unlink(filepath)
   280             except Exception as ex:
   266             except Exception as ex:
   281                 self.error("can't remove %s: %s" % (filepath, ex))
   267                 self.error("can't remove %s: %s" % (filepath, ex))
   282 
   268 
   283 class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
   269 class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
   284     def postcommit_event(self):
   270     def postcommit_event(self):
   285         for filepath in self.get_data():
   271         for filepath in self.get_data():
   286             assert isinstance(filepath, str)  # bytes on py2, unicode on py3
   272             assert isinstance(filepath, str)
   287             try:
   273             try:
   288                 unlink(filepath)
   274                 unlink(filepath)
   289             except Exception as ex:
   275             except Exception as ex:
   290                 self.error("can't remove %s: %s" % (filepath, ex))
   276                 self.error("can't remove %s: %s" % (filepath, ex))