storages.py 4.1 KB
Newer Older
1
2
3
"""custom storages for the system source"""
from os import unlink, path as osp

4
from cubicweb import Binary
5
6
7
8
9
10
11
12
from cubicweb.server.hook import Operation


ETYPE_ATTR_STORAGE = {}
def set_attribute_storage(repo, etype, attr, storage):
    ETYPE_ATTR_STORAGE.setdefault(etype, {})[attr] = storage
    repo.system_source.map_attribute(etype, attr, storage.sqlgen_callback)

13
14
15
16
def unset_attribute_storage(repo, etype, attr):
    ETYPE_ATTR_STORAGE.setdefault(etype, {}).pop(attr, None)
    repo.system_source.unmap_attribute(etype, attr)

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

class Storage(object):
    """abstract storage"""
    def sqlgen_callback(self, generator, relation, linkedvar):
        """sql generator callback when some attribute with a custom storage is
        accessed
        """
        raise NotImplementedError()

    def entity_added(self, entity, attr):
        """an entity using this storage for attr has been added"""
        raise NotImplementedError()
    def entity_updated(self, entity, attr):
        """an entity using this storage for attr has been updatded"""
        raise NotImplementedError()
    def entity_deleted(self, entity, attr):
        """an entity using this storage for attr has been deleted"""
        raise NotImplementedError()

# TODO
# * make it configurable without code
# * better file path attribution
Sylvain Thénault's avatar
Sylvain Thénault committed
39
# * handle backup/restore
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

class BytesFileSystemStorage(Storage):
    """store Bytes attribute value on the file system"""
    def __init__(self, defaultdir):
        self.default_directory = defaultdir

    def sqlgen_callback(self, generator, linkedvar, relation):
        """sql generator callback when some attribute with a custom storage is
        accessed
        """
        linkedvar.accept(generator)
        return '_fsopen(%s.cw_%s)' % (
            linkedvar._q_sql.split('.', 1)[0], # table name
            relation.r_type) # attribute name

    def entity_added(self, entity, attr):
        """an entity using this storage for attr has been added"""
        if not entity._cw.transaction_data.get('fs_importing'):
            try:
                value = entity.pop(attr)
            except KeyError:
                pass
            else:
                fpath = self.new_fs_path(entity, attr)
                # bytes storage used to store file's path
                entity[attr]= Binary(fpath)
                file(fpath, 'w').write(value.getvalue())
                AddFileOp(entity._cw, filepath=fpath)
        # else entity[attr] is expected to be an already existant file path

    def entity_updated(self, entity, attr):
        """an entity using this storage for attr has been updatded"""
        try:
            value = entity.pop(attr)
        except KeyError:
            pass
        else:
            fpath = self.current_fs_path(entity, attr)
            UpdateFileOp(entity._cw, filepath=fpath, filedata=value.getvalue())

    def entity_deleted(self, entity, attr):
        """an entity using this storage for attr has been deleted"""
        DeleteFileOp(entity._cw, filepath=self.current_fs_path(entity, attr))

    def new_fs_path(self, entity, attr):
85
        fspath = osp.join(self.default_directory, '%s_%s' % (entity.eid, attr))
86
87
88
89
90
        while osp.exists(fspath):
            fspath = '_' + fspath
        return fspath

    def current_fs_path(self, entity, attr):
91
92
93
94
95
96
97
        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))
        dbmod = sysource.dbapi_module
        return dbmod.process_value(cu.fetchone()[0], [None, dbmod.BINARY],
                                   binarywrap=str)
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117


class AddFileOp(Operation):
    def rollback_event(self):
        try:
            unlink(self.filepath)
        except:
            pass

class DeleteFileOp(Operation):
    def commit_event(self):
        try:
            unlink(self.filepath)
        except:
            pass

class UpdateFileOp(Operation):
    def precommit_event(self):
        try:
            file(self.filepath, 'w').write(self.filedata)
118
119
        except Exception, ex:
            self.exception(str(ex))