fixing a bug where quit messages from different networks were shown if the user has...
[quassel.git] / src / core / abstractsqlstorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "abstractsqlstorage.h"
22
23 #include "logger.h"
24
25 #include <QSqlError>
26 #include <QSqlQuery>
27
28 AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
29   : Storage(parent),
30     _schemaVersion(0)
31 {
32 }
33
34 AbstractSqlStorage::~AbstractSqlStorage() {
35   QHash<QPair<QString, int>, QSqlQuery *>::iterator iter = _queryCache.begin();
36   while(iter != _queryCache.end()) {
37     delete *iter;
38     iter = _queryCache.erase(iter);
39   }
40   
41   {
42     QSqlDatabase db = QSqlDatabase::database("quassel_connection");
43     db.commit();
44     db.close();
45   }
46   QSqlDatabase::removeDatabase("quassel_connection");  
47 }
48
49 QSqlDatabase AbstractSqlStorage::logDb() {
50   QSqlDatabase db = QSqlDatabase::database("quassel_connection");
51   if(db.isValid() && db.isOpen())
52     return db;
53
54   if(!openDb()) {
55     qWarning() << "Unable to Open Database" << displayName();
56     qWarning() << "-" << db.lastError().text();
57   }
58
59   return QSqlDatabase::database("quassel_connection");
60 }
61
62 bool AbstractSqlStorage::openDb() {
63   QSqlDatabase db = QSqlDatabase::database("quassel_connection");
64   if(db.isValid() && !db.isOpen())
65     return db.open();
66
67   db = QSqlDatabase::addDatabase(driverName(), "quassel_connection");
68   db.setDatabaseName(databaseName());
69
70   if(!hostName().isEmpty())
71     db.setHostName(hostName());
72
73   if(!userName().isEmpty()) {
74     db.setUserName(userName());
75     db.setPassword(password());
76   }
77
78   return db.open();
79 }
80
81 bool AbstractSqlStorage::init(const QVariantMap &settings) {
82   Q_UNUSED(settings)
83   QSqlDatabase db = logDb();
84   if(!db.isValid() || !db.isOpen())
85     return false;
86
87   if(installedSchemaVersion() == -1) {
88     qCritical() << "Storage Schema is missing!";
89     return false;
90   }
91
92   if(installedSchemaVersion() > schemaVersion()) {
93     qCritical() << "Installed Schema is newer then any known Version.";
94     return false;
95   }
96   
97   if(installedSchemaVersion() < schemaVersion()) {
98     qWarning() << "Installed Schema is not up to date. Upgrading...";
99     if(!upgradeDb())
100       return false;
101   }
102   
103   quInfo() << "Storage Backend is ready. Quassel Schema Version:" << installedSchemaVersion();
104   return true;
105 }
106
107 void AbstractSqlStorage::sync() {
108   QHash<QPair<QString, int>, QSqlQuery *>::iterator iter = _queryCache.begin();
109   while(iter != _queryCache.end()) {
110     delete *iter;
111     iter = _queryCache.erase(iter);
112   }
113
114   logDb().commit();
115 }
116
117 QString AbstractSqlStorage::queryString(const QString &queryName, int version) {
118   if(version == 0)
119     version = schemaVersion();
120     
121   QFileInfo queryInfo(QString(":/SQL/%1/%2/%3.sql").arg(displayName()).arg(version).arg(queryName));
122   if(!queryInfo.exists() || !queryInfo.isFile() || !queryInfo.isReadable()) {
123     qCritical() << "Unable to read SQL-Query" << queryName << "for engine" << displayName();
124     return QString();
125   }
126
127   QFile queryFile(queryInfo.filePath());
128   if(!queryFile.open(QIODevice::ReadOnly | QIODevice::Text))
129     return QString();
130   QString query = QTextStream(&queryFile).readAll();
131   queryFile.close();
132   
133   return query.trimmed();
134 }
135
136 QString AbstractSqlStorage::queryString(const QString &queryName) {
137   return queryString(queryName, 0);
138 }
139
140 QSqlQuery *AbstractSqlStorage::cachedQuery(const QString &queryName, int version) {
141   QPair<QString, int> queryId = qMakePair(queryName, version);
142   if(!_queryCache.contains(queryId)) {
143     QSqlQuery *query = new QSqlQuery(logDb());
144     query->prepare(queryString(queryName, version));
145     _queryCache[queryId] = query;
146   }
147   return _queryCache[queryId];
148 }
149
150 QSqlQuery *AbstractSqlStorage::cachedQuery(const QString &queryName) {
151   return cachedQuery(queryName, 0);
152 }
153
154 QStringList AbstractSqlStorage::setupQueries() {
155   QStringList queries;
156   QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(schemaVersion()));
157   foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "setup*", QDir::NoFilter, QDir::Name)) {
158     queries << queryString(fileInfo.baseName());
159   }
160   return queries;
161 }
162
163 bool AbstractSqlStorage::setup(const QVariantMap &settings) {
164   Q_UNUSED(settings)
165   QSqlDatabase db = logDb();
166   if(!db.isOpen()) {
167     qCritical() << "Unable to setup Logging Backend!";
168     return false;
169   }
170
171   foreach(QString queryString, setupQueries()) {
172     QSqlQuery query = db.exec(queryString);
173     if(!watchQuery(&query)) {
174       qCritical() << "Unable to setup Logging Backend!";
175       return false;
176     }
177   }
178   return true;
179 }
180
181 QStringList AbstractSqlStorage::upgradeQueries(int version) {
182   QStringList queries;
183   QDir dir = QDir(QString(":/SQL/%1/%2/").arg(displayName()).arg(version));
184   foreach(QFileInfo fileInfo, dir.entryInfoList(QStringList() << "upgrade*", QDir::NoFilter, QDir::Name)) {
185     queries << queryString(fileInfo.baseName(), version);
186   }
187   return queries;
188 }
189
190 bool AbstractSqlStorage::upgradeDb() {
191   if(schemaVersion() <= installedSchemaVersion())
192     return true;
193
194   QSqlDatabase db = logDb();
195
196   for(int ver = installedSchemaVersion() + 1; ver <= schemaVersion(); ver++) {
197     foreach(QString queryString, upgradeQueries(ver)) {
198       QSqlQuery query = db.exec(queryString);
199       if(!watchQuery(&query)) {
200         qCritical() << "Unable to upgrade Logging Backend!";
201         return false;
202       }
203     }
204   }
205   return true;
206 }
207
208
209 int AbstractSqlStorage::schemaVersion() {
210   // returns the newest Schema Version!
211   // not the currently used one! (though it can be the same)
212   if(_schemaVersion > 0)
213     return _schemaVersion;
214
215   int version;
216   bool ok;
217   QDir dir = QDir(":/SQL/" + displayName());
218   foreach(QFileInfo fileInfo, dir.entryInfoList()) {
219     if(!fileInfo.isDir())
220       continue;
221
222     version = fileInfo.fileName().toInt(&ok);
223     if(!ok)
224       continue;
225
226     if(version > _schemaVersion)
227       _schemaVersion = version;
228   }
229   return _schemaVersion;
230 }
231
232 bool AbstractSqlStorage::watchQuery(QSqlQuery *query) {
233   if(query->lastError().isValid()) {
234     qCritical() << "unhandled Error in QSqlQuery!";
235     qCritical() << "                  last Query:\n" << query->lastQuery();
236     qCritical() << "              executed Query:\n" << query->executedQuery();
237     qCritical() << "                bound Values:";
238     QList<QVariant> list = query->boundValues().values();
239     for (int i = 0; i < list.size(); ++i)
240       qCritical() << i << ": " << list.at(i).toString().toAscii().data();
241     qCritical() << "                Error Number:"   << query->lastError().number();
242     qCritical() << "               Error Message:"   << query->lastError().text();
243     qCritical() << "              Driver Message:"   << query->lastError().driverText();
244     qCritical() << "                  DB Message:"   << query->lastError().databaseText();
245     
246     return false;
247   }
248   return true;
249 }