server/sources/storages.py
changeset 5238 31c12863fd9d
parent 5219 35d44017c72b
child 5396 78d92a47a4e5
child 5421 8167de96c523
--- a/server/sources/storages.py	Tue Apr 13 19:43:30 2010 +0200
+++ b/server/sources/storages.py	Tue Apr 13 19:43:51 2010 +0200
@@ -1,6 +1,8 @@
 """custom storages for the system source"""
 from os import unlink, path as osp
 
+from yams.schema import role_name
+
 from cubicweb import Binary
 from cubicweb.server.hook import Operation
 
@@ -54,10 +56,27 @@
 # * better file path attribution
 # * handle backup/restore
 
+def uniquify_path(dirpath, basename):
+    """return a unique file name for `basename` in `dirpath`, or None
+    if all attemps failed.
+
+    XXX subject to race condition.
+    """
+    path = osp.join(dirpath, basename)
+    if not osp.isfile(path):
+        return path
+    base, ext = osp.splitext(path)
+    for i in xrange(1, 256):
+        path = '%s%s%s' % (base, i, ext)
+        if not osp.isfile(path):
+            return path
+    return None
+
 class BytesFileSystemStorage(Storage):
     """store Bytes attribute value on the file system"""
-    def __init__(self, defaultdir):
+    def __init__(self, defaultdir, fsencoding='utf-8'):
         self.default_directory = defaultdir
+        self.fsencoding = fsencoding
 
     def callback(self, source, value):
         """sql generator callback when some attribute with a custom storage is
@@ -102,16 +121,27 @@
         DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))
 
     def new_fs_path(self, entity, attr):
-        fspath = osp.join(self.default_directory, '%s_%s' % (entity.eid, attr))
-        while osp.exists(fspath):
-            fspath = '_' + fspath
+        # We try to get some hint about how to name the file using attribute's
+        # name metadata, so we use the real file name and extension when
+        # available. Keeping the extension is useful for example in the case of
+        # PIL processing that use filename extension to detect content-type, as
+        # well as providing more understandable file names on the fs.
+        basename = [str(entity.eid), attr]
+        name = entity.attr_metadata(attr, 'name')
+        if name is not None:
+            basename.append(name.encode(self.fsencoding))
+        fspath = uniquify_path(self.default_directory, '_'.join(basename))
+        if fspath is None:
+            msg = entity._cw._('failed to uniquify path (%s, %s)') % (
+                dirpath, '_'.join(basename))
+            raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
         return fspath
 
     def current_fs_path(self, entity, attr):
         sysource = entity._cw.pool.source('system')
         cu = sysource.doexec(entity._cw,
                              'SELECT cw_%s FROM cw_%s WHERE cw_eid=%s' % (
-                                 attr, entity.__regid__, entity.eid))
+                             attr, entity.__regid__, entity.eid))
         rawvalue = cu.fetchone()[0]
         if rawvalue is None: # no previous value
             return self.new_fs_path(entity, attr)