|
1 """custom storages for the system source""" |
|
2 from os import unlink, path as osp |
|
3 |
|
4 from cubicweb.server.hook import Operation |
|
5 |
|
6 |
|
7 ETYPE_ATTR_STORAGE = {} |
|
8 def set_attribute_storage(repo, etype, attr, storage): |
|
9 ETYPE_ATTR_STORAGE.setdefault(etype, {})[attr] = storage |
|
10 repo.system_source.map_attribute(etype, attr, storage.sqlgen_callback) |
|
11 |
|
12 |
|
13 class Storage(object): |
|
14 """abstract storage""" |
|
15 def sqlgen_callback(self, generator, relation, linkedvar): |
|
16 """sql generator callback when some attribute with a custom storage is |
|
17 accessed |
|
18 """ |
|
19 raise NotImplementedError() |
|
20 |
|
21 def entity_added(self, entity, attr): |
|
22 """an entity using this storage for attr has been added""" |
|
23 raise NotImplementedError() |
|
24 def entity_updated(self, entity, attr): |
|
25 """an entity using this storage for attr has been updatded""" |
|
26 raise NotImplementedError() |
|
27 def entity_deleted(self, entity, attr): |
|
28 """an entity using this storage for attr has been deleted""" |
|
29 raise NotImplementedError() |
|
30 |
|
31 # TODO |
|
32 # * make it configurable without code |
|
33 # * better file path attribution |
|
34 |
|
35 class BytesFileSystemStorage(Storage): |
|
36 """store Bytes attribute value on the file system""" |
|
37 def __init__(self, defaultdir): |
|
38 self.default_directory = defaultdir |
|
39 |
|
40 def sqlgen_callback(self, generator, linkedvar, relation): |
|
41 """sql generator callback when some attribute with a custom storage is |
|
42 accessed |
|
43 """ |
|
44 linkedvar.accept(generator) |
|
45 return '_fsopen(%s.cw_%s)' % ( |
|
46 linkedvar._q_sql.split('.', 1)[0], # table name |
|
47 relation.r_type) # attribute name |
|
48 |
|
49 def entity_added(self, entity, attr): |
|
50 """an entity using this storage for attr has been added""" |
|
51 if not entity._cw.transaction_data.get('fs_importing'): |
|
52 try: |
|
53 value = entity.pop(attr) |
|
54 except KeyError: |
|
55 pass |
|
56 else: |
|
57 fpath = self.new_fs_path(entity, attr) |
|
58 # bytes storage used to store file's path |
|
59 entity[attr]= Binary(fpath) |
|
60 file(fpath, 'w').write(value.getvalue()) |
|
61 AddFileOp(entity._cw, filepath=fpath) |
|
62 # else entity[attr] is expected to be an already existant file path |
|
63 |
|
64 def entity_updated(self, entity, attr): |
|
65 """an entity using this storage for attr has been updatded""" |
|
66 try: |
|
67 value = entity.pop(attr) |
|
68 except KeyError: |
|
69 pass |
|
70 else: |
|
71 fpath = self.current_fs_path(entity, attr) |
|
72 UpdateFileOp(entity._cw, filepath=fpath, filedata=value.getvalue()) |
|
73 |
|
74 def entity_deleted(self, entity, attr): |
|
75 """an entity using this storage for attr has been deleted""" |
|
76 DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr)) |
|
77 |
|
78 def new_fs_path(self, entity, attr): |
|
79 fpath = osp.join(self.default_directory, '%s_%s_%s' % ( |
|
80 self.default_directory, entity.eid, attr)) |
|
81 while osp.exists(fspath): |
|
82 fspath = '_' + fspath |
|
83 return fspath |
|
84 |
|
85 def current_fs_path(self, entity, attr): |
|
86 cu = entity._cw.system_sql('SELECT cw_%s.%s WHERE cw_eid=%s' % |
|
87 (entity.__regid__, attr, entity.eid)) |
|
88 return cu.fetchone()[0] |
|
89 |
|
90 |
|
91 class AddFileOp(Operation): |
|
92 def rollback_event(self): |
|
93 try: |
|
94 unlink(self.filepath) |
|
95 except: |
|
96 pass |
|
97 |
|
98 class DeleteFileOp(Operation): |
|
99 def commit_event(self): |
|
100 try: |
|
101 unlink(self.filepath) |
|
102 except: |
|
103 pass |
|
104 |
|
105 class UpdateFileOp(Operation): |
|
106 def precommit_event(self): |
|
107 try: |
|
108 file(self.filepath, 'w').write(self.filedata) |
|
109 except: |
|
110 pass |