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