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) |