3 # Copyright (C) 2013 Simo Sorce <simo@redhat.com>
5 # see file 'COPYING' for use and warranty information
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.
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.
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/>.
21 from ipsilon.util.log import Log
22 from sqlalchemy import create_engine
23 from sqlalchemy import MetaData, Table, Column, Text
24 from sqlalchemy.sql import select
30 OPTIONS_COLUMNS = ['name', 'option', 'value']
31 UNIQUE_DATA_COLUMNS = ['uuid', 'name', 'value']
36 def __init__(self, name):
38 if '://' not in engine_name:
39 engine_name = 'sqlite:///' + engine_name
40 self._dbengine = create_engine(engine_name)
41 self.is_readonly = False
47 return self._dbengine.connect()
51 def at(self, *args, **kwargs):
53 return f(self, *args, **kwargs)
59 def __init__(self, db_obj, table, columns, trans=True):
61 self._con = self._db.connection()
62 self._trans = self._con.begin() if trans else None
63 self._table = self._get_table(table, columns)
65 def _get_table(self, name, columns):
66 table = Table(name, MetaData(self._db.engine()))
68 table.append_column(Column(c, Text()))
71 def _where(self, kvfilter):
73 if kvfilter is not None:
75 w = self._table.columns[k] == kvfilter[k]
82 def _columns(self, columns=None):
84 if columns is not None:
87 cols.append(self._table.columns[c])
89 cols = self._table.columns
93 self._trans.rollback()
99 self._table.create(checkfirst=True)
102 self._table.drop(checkfirst=True)
105 def select(self, kvfilter=None, columns=None):
106 return self._con.execute(select(self._columns(columns),
107 self._where(kvfilter)))
110 def insert(self, values):
111 self._con.execute(self._table.insert(values))
114 def update(self, values, kvfilter):
115 self._con.execute(self._table.update(self._where(kvfilter), values))
118 def delete(self, kvfilter):
119 self._con.execute(self._table.delete(self._where(kvfilter)))
122 class FileStore(Log):
124 def __init__(self, name):
125 self._filename = name
126 self.is_readonly = True
127 self._timestamp = None
130 def get_config(self):
132 stat = os.stat(self._filename)
134 self.error("Unable to check config file %s: [%s]" % (
138 timestamp = stat.st_mtime
139 if self._config is None or timestamp > self._timestamp:
140 self._config = ConfigParser.RawConfigParser()
141 self._config.read(self._filename)
145 class FileQuery(Log):
147 def __init__(self, fstore, table, columns, trans=True):
148 self._fstore = fstore
149 self._config = fstore.get_config()
150 self._section = table
151 if len(columns) > 3 or columns[-1] != 'value':
152 raise ValueError('Unsupported configuration format')
153 self._columns = columns
162 raise NotImplementedError
165 raise NotImplementedError
167 def select(self, kvfilter=None, columns=None):
168 if self._section not in self._config.sections():
171 opts = self._config.options(self._section)
175 if self._columns[0] in kvfilter:
176 prefix = kvfilter[self._columns[0]]
177 prefix_ = prefix + ' '
180 if len(self._columns) == 3 and self._columns[1] in kvfilter:
181 name = kvfilter[self._columns[1]]
184 if self._columns[-1] in kvfilter:
185 value = kvfilter[self._columns[-1]]
189 if len(self._columns) == 3:
191 if prefix and not o.startswith(prefix_):
194 col1, col2 = o.split(' ', 1)
195 if name and col2 != name:
198 col3 = self._config.get(self._section, o)
199 if value and col3 != value:
202 r = [col1, col2, col3]
205 if prefix and o != prefix:
207 r = [o, self._config.get(self._section, o)]
212 s.append(r[self._columns.index(c)])
217 self.debug('SELECT(%s, %s, %s) -> %s' % (self._section,
223 def insert(self, values):
224 raise NotImplementedError
226 def update(self, values, kvfilter):
227 raise NotImplementedError
229 def delete(self, kvfilter):
230 raise NotImplementedError
234 def __init__(self, config_name=None, database_url=None):
235 if config_name is None and database_url is None:
236 raise ValueError('config_name or database_url must be provided')
238 if config_name not in cherrypy.config:
239 raise NameError('Unknown database %s' % config_name)
240 name = cherrypy.config[config_name]
243 if name.startswith('configfile://'):
244 _, filename = name.split('://')
245 self._db = FileStore(filename)
246 self._query = FileQuery
248 self._db = SqlStore(name)
249 self._query = SqlQuery
252 def is_readonly(self):
253 return self._db.is_readonly
255 def _row_to_dict_tree(self, data, row):
261 self._row_to_dict_tree(d2, row[1:])
265 if data[name] is list:
266 data[name].append(value)
269 data[name] = [v, value]
273 def _rows_to_dict_tree(self, rows):
276 self._row_to_dict_tree(data, r)
279 def _load_data(self, table, columns, kvfilter=None):
282 q = self._query(self._db, table, columns, trans=False)
283 rows = q.select(kvfilter)
284 except Exception, e: # pylint: disable=broad-except
285 self.error("Failed to load data for table %s: [%s]" % (table, e))
286 return self._rows_to_dict_tree(rows)
288 def load_config(self):
290 columns = ['name', 'value']
291 return self._load_data(table, columns)
293 def load_options(self, table, name=None):
296 kvfilter['name'] = name
297 options = self._load_data(table, OPTIONS_COLUMNS, kvfilter)
298 if name and name in options:
302 def save_options(self, table, name, options):
306 q = self._query(self._db, table, OPTIONS_COLUMNS)
307 rows = q.select({'name': name}, ['option', 'value'])
309 curvals[row[0]] = row[1]
313 q.update({'value': options[opt]},
314 {'name': name, 'option': opt})
316 q.insert((name, opt, options[opt]))
319 except Exception, e: # pylint: disable=broad-except
322 self.error("Failed to save options: [%s]" % e)
325 def delete_options(self, table, name, options=None):
326 kvfilter = {'name': name}
329 q = self._query(self._db, table, OPTIONS_COLUMNS)
334 kvfilter['option'] = opt
337 except Exception, e: # pylint: disable=broad-except
340 self.error("Failed to delete from %s: [%s]" % (table, e))
343 def new_unique_data(self, table, data):
344 newid = str(uuid.uuid4())
347 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
349 q.insert((newid, name, data[name]))
351 except Exception, e: # pylint: disable=broad-except
354 self.error("Failed to store %s data: [%s]" % (table, e))
358 def get_unique_data(self, table, uuidval=None, name=None, value=None):
361 kvfilter['uuid'] = uuidval
363 kvfilter['name'] = name
365 kvfilter['value'] = value
366 return self._load_data(table, UNIQUE_DATA_COLUMNS, kvfilter)
368 def save_unique_data(self, table, data):
371 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
374 rows = q.select({'uuid': uid}, ['name', 'value'])
381 q.update({'value': datum[name]},
382 {'uuid': uid, 'name': name})
384 q.insert((uid, name, datum[name]))
387 except Exception, e: # pylint: disable=broad-except
390 self.error("Failed to store data in %s: [%s]" % (table, e))
393 def del_unique_data(self, table, uuidval):
394 kvfilter = {'uuid': uuidval}
396 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS, trans=False)
398 except Exception, e: # pylint: disable=broad-except
399 self.error("Failed to delete data from %s: [%s]" % (table, e))
401 def _reset_data(self, table):
403 q = self._query(self._db, table, UNIQUE_DATA_COLUMNS)
407 except Exception, e: # pylint: disable=broad-except
410 self.error("Failed to erase all data from %s: [%s]" % (table, e))
413 class AdminStore(Store):
416 super(AdminStore, self).__init__('admin.config.db')
418 def get_data(self, plugin, idval=None, name=None, value=None):
419 return self.get_unique_data(plugin+"_data", idval, name, value)
421 def save_data(self, plugin, data):
422 return self.save_unique_data(plugin+"_data", data)
424 def new_datum(self, plugin, datum):
425 table = plugin+"_data"
426 return self.new_unique_data(table, datum)
428 def del_datum(self, plugin, idval):
429 table = plugin+"_data"
430 return self.del_unique_data(table, idval)
432 def wipe_data(self, plugin):
433 table = plugin+"_data"
434 self._reset_data(table)
437 class UserStore(Store):
439 def __init__(self, path=None):
440 super(UserStore, self).__init__('user.prefs.db')
442 def save_user_preferences(self, user, options):
443 self.save_options('users', user, options)
445 def load_user_preferences(self, user):
446 return self.load_options('users', user)
448 def save_plugin_data(self, plugin, user, options):
449 self.save_options(plugin+"_data", user, options)
451 def load_plugin_data(self, plugin, user):
452 return self.load_options(plugin+"_data", user)
455 class TranStore(Store):
457 def __init__(self, path=None):
458 super(TranStore, self).__init__('transactions.db')