Move wipe_data into Store() as reset_data
[cascardo/ipsilon.git] / ipsilon / util / data.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2013  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 import sqlite3
21 import cherrypy
22 from ipsilon.util.log import Log
23 import uuid
24
25
26 OPTIONS_COLUMNS = ['name', 'option', 'value']
27 UNIQUE_DATA_COLUMNS = ['uuid', 'name', 'value']
28
29
30 class Store(Log):
31
32     def __init__(self, config_name):
33         if config_name not in cherrypy.config:
34             raise NameError('Unknown database type %s' % config_name)
35         self._dbname = cherrypy.config[config_name]
36
37     def _build_where(self, kvfilter, kvout):
38         where = ""
39         sep = "WHERE"
40         for k in kvfilter:
41             mk = "where_%s" % k
42             kvout[mk] = kvfilter[k]
43             where += "%s %s=:%s" % (sep, k, mk)
44             sep = " AND"
45         return where
46
47     def _build_select(self, table, kvfilter=None, kvout=None, columns=None):
48         SELECT = "SELECT %(cols)s FROM %(table)s %(where)s"
49         cols = "*"
50         if columns:
51             cols = ",".join(columns)
52         where = ""
53         if kvfilter is not None:
54             where = self._build_where(kvfilter, kvout)
55         return SELECT % {'table': table, 'cols': cols, 'where': where}
56
57     def _select(self, cursor, table, kvfilter=None, columns=None):
58         kv = dict()
59         select = self._build_select(table, kvfilter, kv, columns)
60         cursor.execute(select, kv)
61         return cursor.fetchall()
62
63     def _create(self, cursor, table, columns):
64         CREATE = "CREATE TABLE IF NOT EXISTS %(table)s(%(cols)s)"
65         cols = ",".join(columns)
66         create = CREATE % {'table': table, 'cols': cols}
67         cursor.execute(create)
68
69     def _drop(self, cursor, table):
70         cursor.execute("DROP TABLE IF EXISTS " + table)
71
72     def _update(self, cursor, table, values, kvfilter):
73         UPDATE = "UPDATE %(table)s SET %(setval)s %(where)s"
74         kv = dict()
75
76         setval = ""
77         sep = ""
78         for k in values:
79             mk = "setval_%s" % k
80             kv[mk] = values[k]
81             setval += "%s%s=:%s" % (sep, k, mk)
82             sep = " , "
83
84         where = self._build_where(kvfilter, kv)
85
86         update = UPDATE % {'table': table, 'setval': setval, 'where': where}
87         cursor.execute(update, kv)
88
89     def _insert(self, cursor, table, values):
90         INSERT = "INSERT INTO %(table)s VALUES(%(values)s)"
91         vals = ""
92         sep = ""
93         for _ in values:
94             vals += "%s?" % sep
95             sep = ","
96         insert = INSERT % {'table': table, 'values': vals}
97         cursor.execute(insert, values)
98
99     def _delete(self, cursor, table, kvfilter):
100         DELETE = "DELETE FROM %(table)s %(where)s"
101         kv = dict()
102         where = self._build_where(kvfilter, kv)
103         delete = DELETE % {'table': table, 'where': where}
104         cursor.execute(delete, kv)
105
106     def _row_to_dict_tree(self, data, row):
107         name = row[0]
108         if len(row) > 2:
109             if name not in data:
110                 data[name] = dict()
111             d2 = data[name]
112             self._row_to_dict_tree(d2, row[1:])
113         else:
114             value = row[1]
115             if name in data:
116                 if data[name] is list:
117                     data[name].append(value)
118                 else:
119                     v = data[name]
120                     data[name] = [v, value]
121             else:
122                 data[name] = value
123
124     def _rows_to_dict_tree(self, rows):
125         data = dict()
126         for r in rows:
127             self._row_to_dict_tree(data, r)
128         return data
129
130     def _load_data(self, table, columns, kvfilter=None):
131         con = None
132         rows = []
133         try:
134             con = sqlite3.connect(self._dbname)
135             cur = con.cursor()
136             self._create(cur, table, columns)
137             rows = self._select(cur, table, kvfilter)
138             con.commit()
139         except sqlite3.Error, e:
140             if con:
141                 con.rollback()
142             self.error("Failed to load data for table %s: [%s]" % (table, e))
143         finally:
144             if con:
145                 con.close()
146
147         return self._rows_to_dict_tree(rows)
148
149     def load_config(self):
150         table = 'config'
151         columns = ['name', 'value']
152         return self._load_data(table, columns)
153
154     def load_options(self, table, name=None):
155         kvfilter = dict()
156         if name:
157             kvfilter['name'] = name
158         options = self._load_data(table, OPTIONS_COLUMNS, kvfilter)
159         if name and name in options:
160             return options[name]
161         return options
162
163     def save_options(self, table, name, options):
164         curvals = dict()
165         con = None
166         try:
167             con = sqlite3.connect(self._dbname)
168             cur = con.cursor()
169             self._create(cur, table, OPTIONS_COLUMNS)
170             rows = self._select(cur, table, {'name': name},
171                                 ['option', 'value'])
172             for row in rows:
173                 curvals[row[0]] = row[1]
174
175             for opt in options:
176                 if opt in curvals:
177                     self._update(cur, table,
178                                  {'value': options[opt]},
179                                  {'name': name, 'option': opt})
180                 else:
181                     self._insert(cur, table, (name, opt, options[opt]))
182
183             con.commit()
184         except sqlite3.Error, e:
185             if con:
186                 con.rollback()
187             self.error("Failed to store config: [%s]" % e)
188             raise
189         finally:
190             if con:
191                 con.close()
192
193     def delete_options(self, table, name, options=None):
194         kvfilter = {'name': name}
195         try:
196             con = sqlite3.connect(self._dbname)
197             cur = con.cursor()
198             self._create(cur, table, OPTIONS_COLUMNS)
199             if options is None:
200                 self._delete(cur, table, kvfilter)
201             else:
202                 for opt in options:
203                     kvfilter['option'] = opt
204                     self._delete(cur, table, kvfilter)
205             con.commit()
206         except sqlite3.Error, e:
207             if con:
208                 con.rollback()
209             self.error("Failed to delete from %s: [%s]" % (table, e))
210             raise
211         finally:
212             if con:
213                 con.close()
214
215     def new_unique_data(self, table, data):
216         con = None
217         try:
218             con = sqlite3.connect(self._dbname)
219             cur = con.cursor()
220             self._create(cur, table, UNIQUE_DATA_COLUMNS)
221             newid = str(uuid.uuid4())
222             for name in data:
223                 self._insert(cur, table, (newid, name, data[name]))
224             con.commit()
225         except sqlite3.Error, e:
226             if con:
227                 con.rollback()
228             cherrypy.log.error("Failed to store %s data: [%s]" % (table, e))
229             raise
230         finally:
231             if con:
232                 con.close()
233         return newid
234
235     def get_unique_data(self, table, uuidval=None, name=None, value=None):
236         kvfilter = dict()
237         if uuidval:
238             kvfilter['uuid'] = uuidval
239         if name:
240             kvfilter['name'] = name
241         if value:
242             kvfilter['value'] = value
243         return self._load_data(table, UNIQUE_DATA_COLUMNS, kvfilter)
244
245     def save_unique_data(self, table, data):
246         curvals = dict()
247         con = None
248         try:
249             con = sqlite3.connect(self._dbname)
250             cur = con.cursor()
251             self._create(cur, table, UNIQUE_DATA_COLUMNS)
252             for uid in data:
253                 curvals = dict()
254                 rows = self._select(cur, table, {'uuid': uid},
255                                     ['name', 'value'])
256                 for r in rows:
257                     curvals[r[0]] = r[1]
258
259                 datum = data[uid]
260                 for name in datum:
261                     if name in curvals:
262                         self._update(cur, table,
263                                      {'value': datum[name]},
264                                      {'uuid': uid, 'name': name})
265                     else:
266                         self._insert(cur, table, (uid, name, datum[name]))
267
268             con.commit()
269         except sqlite3.Error, e:
270             if con:
271                 con.rollback()
272             self.error("Failed to store data in %s: [%s]" % (table, e))
273             raise
274         finally:
275             if con:
276                 con.close()
277
278     def del_unique_data(self, table, uuidval):
279         kvfilter = {'uuid': uuidval}
280         con = None
281         try:
282             con = sqlite3.connect(self._dbname)
283             cur = con.cursor()
284             self._delete(cur, table, kvfilter)
285         except sqlite3.Error, e:
286             self.error("Failed to delete data from %s: [%s]" % (table, e))
287         finally:
288             if con:
289                 con.close()
290
291     def reset_data(self, table):
292         try:
293             con = sqlite3.connect(self._dbname)
294             cur = con.cursor()
295             self._drop(cur, table)
296             self._create(cur, table, UNIQUE_DATA_COLUMNS)
297             con.commit()
298         except sqlite3.Error, e:
299             if con:
300                 con.rollback()
301             self.error("Failed to erase all data from %s: [%s]" % (table, e))
302         finally:
303             if con:
304                 con.close()
305
306
307 class AdminStore(Store):
308
309     def __init__(self):
310         super(AdminStore, self).__init__('admin.config.db')
311
312     def get_data(self, plugin, idval=None, name=None, value=None):
313         return self.get_unique_data(plugin+"_data", idval, name, value)
314
315     def save_data(self, plugin, data):
316         return self.save_unique_data(plugin+"_data", data)
317
318     def new_datum(self, plugin, datum):
319         table = plugin+"_data"
320         return self.new_unique_data(table, datum)
321
322     def del_datum(self, plugin, idval):
323         table = plugin+"_data"
324         return self.del_unique_data(table, idval)
325
326     def wipe_data(self, plugin):
327         table = plugin+"_data"
328         self.reset_data(table)
329
330
331 class UserStore(Store):
332
333     def __init__(self, path=None):
334         super(UserStore, self).__init__('user.prefs.db')
335
336     def save_user_preferences(self, user, options):
337         return self.save_options('users', user, options)
338
339
340 class TranStore(Store):
341
342     def __init__(self, path=None):
343         super(TranStore, self).__init__('transactions.db')