1 # Copyright (C) 2013 Simo Sorce <simo@redhat.com>
3 # see file 'COPYING' for use and warranty information
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from ipsilon.util.log import Log
20 from sqlalchemy import create_engine
21 from sqlalchemy import MetaData, Table, Column, Text
22 from sqlalchemy.sql import select
28 OPTIONS_COLUMNS = ['name', 'option', 'value']
29 UNIQUE_DATA_COLUMNS = ['uuid', 'name', 'value']
34 def __init__(self, name):
36 if '://' not in engine_name:
37 engine_name = 'sqlite:///' + engine_name
38 self._dbengine = create_engine(engine_name)
39 self.is_readonly = False
45 return self._dbengine.connect()
49 def at(self, *args, **kwargs):
51 return f(self, *args, **kwargs)
57 def __init__(self, db_obj, table, columns, trans=True):
59 self._con = self._db.connection()
60 self._trans = self._con.begin() if trans else None
61 self._table = self._get_table(table, columns)
63 def _get_table(self, name, columns):
64 table = Table(name, MetaData(self._db.engine()))
66 table.append_column(Column(c, Text()))
69 def _where(self, kvfilter):
71 if kvfilter is not None:
73 w = self._table.columns[k] == kvfilter[k]
80 def _columns(self, columns=None):
82 if columns is not None:
85 cols.append(self._table.columns[c])
87 cols = self._table.columns
91 self._trans.rollback()
97 self._table.create(checkfirst=True)
100 self._table.drop(checkfirst=True)
103 def select(self, kvfilter=None, columns=None):
104 return self._con.execute(select(self._columns(columns),
105 self._where(kvfilter)))
108 def insert(self, values):
109 self._con.execute(self._table.insert(values))
112 def update(self, values, kvfilter):
113 self._con.execute(self._table.update(self._where(kvfilter), values))
116 def delete(self, kvfilter):
117 self._con.execute(self._table.delete(self._where(kvfilter)))
120 class FileStore(Log):
122 def __init__(self, name):
123 self._filename = name
124 self.is_readonly = True
125 self._timestamp = None
128 def get_config(self):
130 stat = os.stat(self._filename)
132 self.error("Unable to check config file %s: [%s]" % (
136 timestamp = stat.st_mtime
137 if self._config is None or timestamp > self._timestamp:
138 self._config = ConfigParser.RawConfigParser()
139 self._config.read(self._filename)
143 class FileQuery(Log):
145 def __init__(self, fstore, table, columns, trans=True):
146 self._fstore = fstore
147 self._config = fstore.get_config()
148 self._section = table
149 if len(columns) > 3 or columns[-1] != 'value':
150 raise ValueError('Unsupported configuration format')
151 self._columns = columns
160 raise NotImplementedError
163 raise NotImplementedError
165 def select(self, kvfilter=None, columns=None):
166 if self._section not in self._config.sections():
169 opts = self._config.options(self._section)
173 if self._columns[0] in kvfilter:
174 prefix = kvfilter[self._columns[0]]
175 prefix_ = prefix + ' '
178 if len(self._columns) == 3 and self._columns[1] in kvfilter:
179 name = kvfilter[self._columns[1]]
182 if self._columns[-1] in kvfilter:
183 value = kvfilter[self._columns[-1]]
187 if len(self._columns) == 3:
189 if prefix and not o.startswith(prefix_):
192 col1, col2 = o.split(' ', 1)
193 if name and col2 != name:
196 col3 = self._config.get(self._section, o)
197 if value and col3 != value:
200 r = [col1, col2, col3]
203 if prefix and o != prefix:
205 r = [o, self._config.get(self._section, o)]
210 s.append(r[self._columns.index(c)])
215 self.debug('SELECT(%s, %s, %s) -> %s' % (self._section,
221 def insert(self, values):
222 raise NotImplementedError
224 def update(self, values, kvfilter):
225 raise NotImplementedError
227 def delete(self, kvfilter):
228 raise NotImplementedError
232 def __init__(self, config_name=None, database_url=None):
233 if config_name is None and database_url is None:
234 raise ValueError('config_name or database_url must be provided')
236 if config_name not in cherrypy.config:
237 raise NameError('Unknown database %s' % config_name)
238 name = cherrypy.config[config_name]
241 if name.startswith('configfile://'):
242 _, filename = name.split('://')
243 self._db = FileStore(filename)
244 self._query = FileQuery
246 self._db = SqlStore(name)
247 self._query = SqlQuery
250 def is_readonly(self):
251 return self._db.is_readonly
253 def _row_to_dict_tree(self, data, row):
259 self._row_to_dict_tree(d2, row[1:])
263 if data[name] is list:
264 data[name].append(value)
267 data[name] = [v, value]
271 def _rows_to_dict_tree(self, rows):
274 self._row_to_dict_tree(data, r)
277 def _load_data(self, table, columns, kvfilter=None):
280 q = self._query(self._db, table, columns, trans=False)
281 rows = q.select(kvfilter)
282 except Exception, e: # pylint: disable=broad-except
283 self.error("Failed to load data for table %s: [%s]" % (table, e))
284 return self._rows_to_dict_tree(rows)
286 def load_config(self):
288 columns = ['name', 'value']
289 return self._load_data(table, columns)
291 def load_options(self, table, name=None):
294 kvfilter['name'] = name
295 options = self._load_data(table, OPTIONS_COLUMNS, kvfilter)
296 if name and name in options:
300 def save_options(self, table, name, options):
304 q = self._query(self._db, table, OPTIONS_COLUMNS)
305 rows = q.select({'name': name}, ['option', 'value'])
307 curvals[row[0]] = row[1]
311 q.update({'value': options[opt]},
312 {'name': name, 'option': opt})
314 q.insert((name, opt, options[opt]))
317 except Exception, e: # pylint: disable=broad-except
320 self.error("Failed to save options: [%s]" % e)
323 def delete_options(self, table, name, options=None):
324 kvfilter = {'name': name}
327 q = self._query(self._db, table, OPTIONS_COLUMNS)
332 kvfilter['option'] = opt
335 except Exception, e: # pylint: disable=broad-except
338 self.error("Failed to delete from %s: [%s]" % (table, e))
341 def new_unique_data(self, table, data):
342 newid = str(uuid.uuid4())
345 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
347 q.insert((newid, name, data[name]))
349 except Exception, e: # pylint: disable=broad-except
352 self.error("Failed to store %s data: [%s]" % (table, e))
356 def get_unique_data(self, table, uuidval=None, name=None, value=None):
359 kvfilter['uuid'] = uuidval
361 kvfilter['name'] = name
363 kvfilter['value'] = value
364 return self._load_data(table, UNIQUE_DATA_COLUMNS, kvfilter)
366 def save_unique_data(self, table, data):
369 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
372 rows = q.select({'uuid': uid}, ['name', 'value'])
379 q.update({'value': datum[name]},
380 {'uuid': uid, 'name': name})
382 q.insert((uid, name, datum[name]))
385 except Exception, e: # pylint: disable=broad-except
388 self.error("Failed to store data in %s: [%s]" % (table, e))
391 def del_unique_data(self, table, uuidval):
392 kvfilter = {'uuid': uuidval}
394 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS, trans=False)
396 except Exception, e: # pylint: disable=broad-except
397 self.error("Failed to delete data from %s: [%s]" % (table, e))
399 def _reset_data(self, table):
401 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
405 except Exception, e: # pylint: disable=broad-except
408 self.error("Failed to erase all data from %s: [%s]" % (table, e))
411 class AdminStore(Store):
414 super(AdminStore, self).__init__('admin.config.db')
416 def get_data(self, plugin, idval=None, name=None, value=None):
417 return self.get_unique_data(plugin+"_data", idval, name, value)
419 def save_data(self, plugin, data):
420 return self.save_unique_data(plugin+"_data", data)
422 def new_datum(self, plugin, datum):
423 table = plugin+"_data"
424 return self.new_unique_data(table, datum)
426 def del_datum(self, plugin, idval):
427 table = plugin+"_data"
428 return self.del_unique_data(table, idval)
430 def wipe_data(self, plugin):
431 table = plugin+"_data"
432 self._reset_data(table)
435 class UserStore(Store):
437 def __init__(self, path=None):
438 super(UserStore, self).__init__('user.prefs.db')
440 def save_user_preferences(self, user, options):
441 self.save_options('users', user, options)
443 def load_user_preferences(self, user):
444 return self.load_options('users', user)
446 def save_plugin_data(self, plugin, user, options):
447 self.save_options(plugin+"_data", user, options)
449 def load_plugin_data(self, plugin, user):
450 return self.load_options(plugin+"_data", user)
453 class TranStore(Store):
455 def __init__(self, path=None):
456 super(TranStore, self).__init__('transactions.db')