Databases must be configured in cherrypy.config
[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 _update(self, cursor, table, values, kvfilter):
70         UPDATE = "UPDATE %(table)s SET %(setval)s %(where)s"
71         kv = dict()
72
73         setval = ""
74         sep = ""
75         for k in values:
76             mk = "setval_%s" % k
77             kv[mk] = values[k]
78             setval += "%s%s=:%s" % (sep, k, mk)
79             sep = " , "
80
81         where = self._build_where(kvfilter, kv)
82
83         update = UPDATE % {'table': table, 'setval': setval, 'where': where}
84         cursor.execute(update, kv)
85
86     def _insert(self, cursor, table, values):
87         INSERT = "INSERT INTO %(table)s VALUES(%(values)s)"
88         vals = ""
89         sep = ""
90         for _ in values:
91             vals += "%s?" % sep
92             sep = ","
93         insert = INSERT % {'table': table, 'values': vals}
94         cursor.execute(insert, values)
95
96     def _delete(self, cursor, table, kvfilter):
97         DELETE = "DELETE FROM %(table)s %(where)s"
98         kv = dict()
99         where = self._build_where(kvfilter, kv)
100         delete = DELETE % {'table': table, 'where': where}
101         cursor.execute(delete, kv)
102
103     def _row_to_dict_tree(self, data, row):
104         name = row[0]
105         if len(row) > 2:
106             if name not in data:
107                 data[name] = dict()
108             d2 = data[name]
109             self._row_to_dict_tree(d2, row[1:])
110         else:
111             value = row[1]
112             if name in data:
113                 if data[name] is list:
114                     data[name].append(value)
115                 else:
116                     v = data[name]
117                     data[name] = [v, value]
118             else:
119                 data[name] = value
120
121     def _rows_to_dict_tree(self, rows):
122         data = dict()
123         for r in rows:
124             self._row_to_dict_tree(data, r)
125         return data
126
127     def _load_data(self, table, columns, kvfilter=None):
128         con = None
129         rows = []
130         try:
131             con = sqlite3.connect(self._dbname)
132             cur = con.cursor()
133             self._create(cur, table, columns)
134             rows = self._select(cur, table, kvfilter)
135             con.commit()
136         except sqlite3.Error, e:
137             if con:
138                 con.rollback()
139             self.error("Failed to load data for table %s: [%s]" % (table, e))
140         finally:
141             if con:
142                 con.close()
143
144         return self._rows_to_dict_tree(rows)
145
146     def load_config(self):
147         table = 'config'
148         columns = ['name', 'value']
149         return self._load_data(table, columns)
150
151     def load_options(self, table, name=None):
152         kvfilter = dict()
153         if name:
154             kvfilter['name'] = name
155         options = self._load_data(table, OPTIONS_COLUMNS, kvfilter)
156         if name and name in options:
157             return options[name]
158         return options
159
160     def save_options(self, table, name, options):
161         curvals = dict()
162         con = None
163         try:
164             con = sqlite3.connect(self._dbname)
165             cur = con.cursor()
166             self._create(cur, table, OPTIONS_COLUMNS)
167             rows = self._select(cur, table, {'name': name},
168                                 ['option', 'value'])
169             for row in rows:
170                 curvals[row[0]] = row[1]
171
172             for opt in options:
173                 if opt in curvals:
174                     self._update(cur, table,
175                                  {'value': options[opt]},
176                                  {'name': name, 'option': opt})
177                 else:
178                     self._insert(cur, table, (name, opt, options[opt]))
179
180             con.commit()
181         except sqlite3.Error, e:
182             if con:
183                 con.rollback()
184             self.error("Failed to store config: [%s]" % e)
185             raise
186         finally:
187             if con:
188                 con.close()
189
190     def delete_options(self, table, name, options=None):
191         kvfilter = {'name': name}
192         try:
193             con = sqlite3.connect(self._dbname)
194             cur = con.cursor()
195             self._create(cur, table, OPTIONS_COLUMNS)
196             if options is None:
197                 self._delete(cur, table, kvfilter)
198             else:
199                 for opt in options:
200                     kvfilter['option'] = opt
201                     self._delete(cur, table, kvfilter)
202             con.commit()
203         except sqlite3.Error, e:
204             if con:
205                 con.rollback()
206             self.error("Failed to delete from %s: [%s]" % (table, e))
207             raise
208         finally:
209             if con:
210                 con.close()
211
212     def new_unique_data(self, table, data):
213         con = None
214         try:
215             con = sqlite3.connect(self._dbname)
216             cur = con.cursor()
217             self._create(cur, table, UNIQUE_DATA_COLUMNS)
218             newid = str(uuid.uuid4())
219             for name in data:
220                 self._insert(cur, table, (newid, name, data[name]))
221             con.commit()
222         except sqlite3.Error, e:
223             if con:
224                 con.rollback()
225             cherrypy.log.error("Failed to store %s data: [%s]" % (table, e))
226             raise
227         finally:
228             if con:
229                 con.close()
230         return newid
231
232     def get_unique_data(self, table, uuidval=None, name=None, value=None):
233         kvfilter = dict()
234         if uuidval:
235             kvfilter['uuid'] = uuidval
236         if name:
237             kvfilter['name'] = name
238         if value:
239             kvfilter['value'] = value
240         return self._load_data(table, UNIQUE_DATA_COLUMNS, kvfilter)
241
242     def save_unique_data(self, table, data):
243         curvals = dict()
244         con = None
245         try:
246             con = sqlite3.connect(self._dbname)
247             cur = con.cursor()
248             self._create(cur, table, UNIQUE_DATA_COLUMNS)
249             for uid in data:
250                 curvals = dict()
251                 rows = self._select(cur, table, {'uuid': uid},
252                                     ['name', 'value'])
253                 for r in rows:
254                     curvals[r[0]] = r[1]
255
256                 datum = data[uid]
257                 for name in datum:
258                     if name in curvals:
259                         self._update(cur, table,
260                                      {'value': datum[name]},
261                                      {'uuid': uid, 'name': name})
262                     else:
263                         self._insert(cur, table, (uid, name, datum[name]))
264
265             con.commit()
266         except sqlite3.Error, e:
267             if con:
268                 con.rollback()
269             self.error("Failed to store data in %s: [%s]" % (table, e))
270             raise
271         finally:
272             if con:
273                 con.close()
274
275     def del_unique_data(self, table, uuidval):
276         kvfilter = {'uuid': uuidval}
277         con = None
278         try:
279             con = sqlite3.connect(self._dbname)
280             cur = con.cursor()
281             self._delete(cur, table, kvfilter)
282         except sqlite3.Error, e:
283             self.error("Failed to delete data from %s: [%s]" % (table, e))
284         finally:
285             if con:
286                 con.close()
287
288
289 class AdminStore(Store):
290
291     def __init__(self):
292         super(AdminStore, self).__init__('admin.config.db')
293
294     def get_data(self, plugin, idval=None, name=None, value=None):
295         return self.get_unique_data(plugin+"_data", idval, name, value)
296
297     def save_data(self, plugin, data):
298         return self.save_unique_data(plugin+"_data", data)
299
300     def new_datum(self, plugin, datum):
301         table = plugin+"_data"
302         return self.new_unique_data(table, datum)
303
304     def del_datum(self, plugin, idval):
305         table = plugin+"_data"
306         return self.del_unique_data(table, idval)
307
308     def wipe_data(self, plugin):
309         table = plugin+"_data"
310         # Try to backup old data first, just in case
311         try:
312             con = sqlite3.connect(self._dbname)
313             cur = con.cursor()
314             cur.execute("DROP TABLE IF EXISTS " + table)
315             self._create(cur, table, UNIQUE_DATA_COLUMNS)
316             con.commit()
317         except sqlite3.Error, e:
318             if con:
319                 con.rollback()
320             cherrypy.log.error("Failed to wipe %s data: [%s]" % (plugin, e))
321             raise
322         finally:
323             if con:
324                 con.close()
325
326
327 class UserStore(Store):
328
329     def __init__(self, path=None):
330         super(UserStore, self).__init__('user.prefs.db')
331
332     def save_user_preferences(self, user, options):
333         return self.save_options('users', user, options)
334
335
336 class TranStore(Store):
337
338     def __init__(self, path=None):
339         super(TranStore, self).__init__('transactions.db')