1 /***************************************************************************
2 * Copyright (C) 2005-07 by the Quassel IRC Team *
3 * devel@quassel-irc.org *
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. *
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, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include "sqlitestorage.h"
23 #include <QCryptographicHash>
28 SqliteStorage::SqliteStorage(QObject *parent)
29 : AbstractSqlStorage(parent)
33 SqliteStorage::~SqliteStorage() {
36 bool SqliteStorage::isAvailable() const {
37 if(!QSqlDatabase::isDriverAvailable("QSQLITE")) return false;
41 QString SqliteStorage::displayName() const {
42 return QString("SQLite");
45 QString SqliteStorage::description() const {
46 return tr("SQLite is a file-based database engine that does not require any setup. It is suitable for small and medium-sized "
47 "databases that do not require access via network. Use SQLite if your Quassel Core should store its data on the same machine "
48 "it is running on, and if you only expect a few users to use your core.");
51 int SqliteStorage::installedSchemaVersion() {
52 QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
54 return query.value(0).toInt();
56 // maybe it's really old... (schema version 0)
57 query = logDb().exec("SELECT MAX(version) FROM coreinfo");
59 return query.value(0).toInt();
61 return AbstractSqlStorage::installedSchemaVersion();
64 UserId SqliteStorage::addUser(const QString &user, const QString &password) {
65 QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
66 cryptopass = cryptopass.toHex();
68 QSqlQuery query(logDb());
69 query.prepare(queryString("insert_quasseluser"));
70 query.bindValue(":username", user);
71 query.bindValue(":password", cryptopass);
73 if(query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
77 query.prepare(queryString("select_userid"));
78 query.bindValue(":username", user);
81 UserId uid = query.value(0).toInt();
82 emit userAdded(uid, user);
86 void SqliteStorage::updateUser(UserId user, const QString &password) {
87 QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
88 cryptopass = cryptopass.toHex();
90 QSqlQuery query(logDb());
91 query.prepare(queryString("update_userpassword"));
92 query.bindValue(":userid", user.toInt());
93 query.bindValue(":password", cryptopass);
97 void SqliteStorage::renameUser(UserId user, const QString &newName) {
98 QSqlQuery query(logDb());
99 query.prepare(queryString("update_username"));
100 query.bindValue(":userid", user.toInt());
101 query.bindValue(":username", newName);
103 emit userRenamed(user, newName);
106 UserId SqliteStorage::validateUser(const QString &user, const QString &password) {
107 QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
108 cryptopass = cryptopass.toHex();
110 QSqlQuery query(logDb());
111 query.prepare(queryString("select_authuser"));
112 query.bindValue(":username", user);
113 query.bindValue(":password", cryptopass);
117 return query.value(0).toInt();
123 void SqliteStorage::delUser(UserId user) {
124 QSqlQuery query(logDb());
125 query.prepare(queryString("delete_backlog_by_uid"));
126 query.bindValue(":userid", user.toInt());
129 query.prepare(queryString("delete_buffers_by_uid"));
130 query.bindValue(":userid", user.toInt());
133 query.prepare(queryString("delete_networks_by_uid"));
134 query.bindValue(":userid", user.toInt());
137 query.prepare(queryString("delete_quasseluser"));
138 query.bindValue(":userid", user.toInt());
140 // I hate the lack of foreign keys and on delete cascade... :(
141 emit userRemoved(user);
144 NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo &info) {
146 QSqlQuery query(logDb());
147 query.prepare(queryString("insert_network"));
148 query.bindValue(":userid", user.toInt());
149 query.bindValue(":networkname", info.networkName);
152 networkId = getNetworkId(user, info.networkName);
153 if(!networkId.isValid()) {
156 updateNetwork(user, info);
161 bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo &info) {
162 if(!isValidNetwork(user, info.networkId))
165 QSqlQuery updateQuery(logDb());
166 updateQuery.prepare(queryString("update_network"));
167 updateQuery.bindValue(":networkname", info.networkName);
168 updateQuery.bindValue(":identityid", info.identity.toInt());
169 updateQuery.bindValue(":usecustomencoding", info.useCustomEncodings ? 1 : 0);
170 updateQuery.bindValue(":encodingcodec", QString(info.codecForEncoding));
171 updateQuery.bindValue(":decodingcodec", QString(info.codecForDecoding));
172 updateQuery.bindValue(":servercodec", QString(info.codecForServer));
173 updateQuery.bindValue(":userandomserver", info.useRandomServer ? 1 : 0);
174 updateQuery.bindValue(":perform", info.perform.join("\n"));
175 updateQuery.bindValue(":useautoidentify", info.useAutoIdentify ? 1 : 0);
176 updateQuery.bindValue(":autoidentifyservice", info.autoIdentifyService);
177 updateQuery.bindValue(":autoidentifypassword", info.autoIdentifyPassword);
178 updateQuery.bindValue(":useautoreconnect", info.useAutoReconnect ? 1 : 0);
179 updateQuery.bindValue(":autoreconnectinterval", info.autoReconnectInterval);
180 updateQuery.bindValue(":autoreconnectretries", info.autoReconnectRetries);
181 updateQuery.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries ? 1 : 0);
182 updateQuery.bindValue(":rejoinchannels", info.rejoinChannels ? 1 : 0);
183 updateQuery.bindValue(":networkid", info.networkId.toInt());
185 if(!watchQuery(&updateQuery))
188 QSqlQuery dropServersQuery(logDb());
189 dropServersQuery.prepare("DELETE FROM ircserver WHERE networkid = :networkid");
190 dropServersQuery.bindValue(":networkid", info.networkId.toInt());
191 dropServersQuery.exec();
192 if(!watchQuery(&dropServersQuery))
195 QSqlQuery insertServersQuery(logDb());
196 insertServersQuery.prepare(queryString("insert_server"));
197 foreach(QVariant server_, info.serverList) {
198 QVariantMap server = server_.toMap();
199 insertServersQuery.bindValue(":hostname", server["Host"]);
200 insertServersQuery.bindValue(":port", server["Port"].toInt());
201 insertServersQuery.bindValue(":password", server["Password"]);
202 insertServersQuery.bindValue(":ssl", server["UseSSL"].toBool() ? 1 : 0);
203 insertServersQuery.bindValue(":userid", user.toInt());
204 insertServersQuery.bindValue(":networkid", info.networkId.toInt());
206 insertServersQuery.exec();
207 if(!watchQuery(&insertServersQuery))
214 bool SqliteStorage::removeNetwork(UserId user, const NetworkId &networkId) {
215 if(!isValidNetwork(user, networkId))
218 bool withTransaction = logDb().driver()->hasFeature(QSqlDriver::Transactions);
219 if(withTransaction) {
221 if(!logDb().transaction()) {
222 qWarning() << "SqliteStorage::removeNetwork(): cannot start transaction. continuing with out rollback support!";
223 withTransaction = false;
227 QSqlQuery deleteBacklogQuery(logDb());
228 deleteBacklogQuery.prepare(queryString("delete_backlog_for_network"));
229 deleteBacklogQuery.bindValue(":networkid", networkId.toInt());
230 deleteBacklogQuery.exec();
231 if(!watchQuery(&deleteBacklogQuery)) {
237 QSqlQuery deleteBuffersQuery(logDb());
238 deleteBuffersQuery.prepare(queryString("delete_buffers_for_network"));
239 deleteBuffersQuery.bindValue(":networkid", networkId.toInt());
240 deleteBuffersQuery.exec();
241 if(!watchQuery(&deleteBuffersQuery)) {
247 QSqlQuery deleteServersQuery(logDb());
248 deleteServersQuery.prepare(queryString("delete_ircservers_for_network"));
249 deleteServersQuery.bindValue(":networkid", networkId.toInt());
250 deleteServersQuery.exec();
251 if(!watchQuery(&deleteServersQuery)) {
257 QSqlQuery deleteNetworkQuery(logDb());
258 deleteNetworkQuery.prepare(queryString("delete_network"));
259 deleteNetworkQuery.bindValue(":networkid", networkId.toInt());
260 deleteNetworkQuery.exec();
261 if(!watchQuery(&deleteNetworkQuery)) {
271 QList<NetworkInfo> SqliteStorage::networks(UserId user) {
272 QList<NetworkInfo> nets;
274 QSqlQuery networksQuery(logDb());
275 networksQuery.prepare(queryString("select_networks_for_user"));
276 networksQuery.bindValue(":userid", user.toInt());
278 QSqlQuery serversQuery(logDb());
279 serversQuery.prepare(queryString("select_servers_for_network"));
281 networksQuery.exec();
282 if(!watchQuery(&networksQuery))
285 while(networksQuery.next()) {
287 net.networkId = networksQuery.value(0).toInt();
288 net.networkName = networksQuery.value(1).toString();
289 net.identity = networksQuery.value(2).toInt();
290 net.codecForServer = networksQuery.value(3).toString().toAscii();
291 net.codecForEncoding = networksQuery.value(4).toString().toAscii();
292 net.codecForDecoding = networksQuery.value(5).toString().toAscii();
293 net.useRandomServer = networksQuery.value(6).toInt() == 1 ? true : false;
294 net.perform = networksQuery.value(7).toString().split("\n");
295 net.useAutoIdentify = networksQuery.value(8).toInt() == 1 ? true : false;
296 net.autoIdentifyService = networksQuery.value(9).toString();
297 net.autoIdentifyPassword = networksQuery.value(10).toString();
298 net.useAutoReconnect = networksQuery.value(11).toInt() == 1 ? true : false;
299 net.autoReconnectInterval = networksQuery.value(12).toUInt();
300 net.autoReconnectRetries = networksQuery.value(13).toInt();
301 net.unlimitedReconnectRetries = networksQuery.value(14).toInt() == 1 ? true : false;
302 net.rejoinChannels = networksQuery.value(15).toInt() == 1 ? true : false;
304 serversQuery.bindValue(":networkid", net.networkId.toInt());
306 if(!watchQuery(&serversQuery))
309 QVariantList servers;
310 while(serversQuery.next()) {
312 server["Host"] = serversQuery.value(0).toString();
313 server["Port"] = serversQuery.value(1).toInt();
314 server["Password"] = serversQuery.value(2).toString();
315 server["UseSSL"] = serversQuery.value(3).toInt() == 1 ? true : false;
318 net.serverList = servers;
324 bool SqliteStorage::isValidNetwork(UserId user, const NetworkId &networkId) {
325 QSqlQuery query(logDb());
326 query.prepare(queryString("select_networkExists"));
327 query.bindValue(":userid", user.toInt());
328 query.bindValue(":networkid", networkId.toInt());
331 watchQuery(&query); // there should not occur any errors
335 Q_ASSERT(!query.next());
339 bool SqliteStorage::isValidBuffer(const UserId &user, const BufferId &bufferId) {
340 QSqlQuery query(logDb());
341 query.prepare(queryString("select_bufferExists"));
342 query.bindValue(":userid", user.toInt());
343 query.bindValue(":bufferid", bufferId.toInt());
350 Q_ASSERT(!query.next());
354 NetworkId SqliteStorage::getNetworkId(UserId user, const QString &network) {
355 QSqlQuery query(logDb());
356 query.prepare("SELECT networkid FROM network "
357 "WHERE userid = :userid AND networkname = :networkname");
358 query.bindValue(":userid", user.toInt());
359 query.bindValue(":networkname", network);
363 return query.value(0).toInt();
368 void SqliteStorage::createBuffer(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer) {
369 QSqlQuery *query = cachedQuery("insert_buffer");
370 query->bindValue(":userid", user.toInt());
371 query->bindValue(":networkid", networkId.toInt());
372 query->bindValue(":buffertype", (int)type);
373 query->bindValue(":buffername", buffer);
374 query->bindValue(":buffercname", buffer.toLower());
380 BufferInfo SqliteStorage::getBufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer) {
381 QSqlQuery *query = cachedQuery("select_bufferByName");
382 query->bindValue(":networkid", networkId.toInt());
383 query->bindValue(":userid", user.toInt());
384 query->bindValue(":buffercname", buffer.toLower());
387 if(!query->first()) {
388 createBuffer(user, networkId, type, buffer);
390 if(!query->first()) {
392 qWarning() << "unable to create BufferInfo for:" << user << networkId << buffer;
397 BufferInfo bufferInfo = BufferInfo(query->value(0).toInt(), networkId, (BufferInfo::Type)query->value(1).toInt(), 0, buffer);
399 qWarning() << "SqliteStorage::getBufferInfo(): received more then one Buffer!";
400 qWarning() << " Query:" << query->lastQuery();
401 qWarning() << " bound Values:" << query->boundValues();
408 BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId) {
409 QSqlQuery query(logDb());
410 query.prepare(queryString("select_buffer_by_id"));
411 query.bindValue(":userid", user.toInt());
412 query.bindValue(":bufferid", bufferId.toInt());
414 if(!watchQuery(&query))
420 BufferInfo bufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString());
421 Q_ASSERT(!query.next());
426 QList<BufferInfo> SqliteStorage::requestBuffers(UserId user, QDateTime since) {
429 time = since.toTime_t();
431 QList<BufferInfo> bufferlist;
432 QSqlQuery query(logDb());
433 query.prepare(queryString("select_buffers"));
434 query.bindValue(":userid", user.toInt());
435 query.bindValue(":time", time);
439 while(query.next()) {
440 bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString());
445 bool SqliteStorage::removeBuffer(const UserId &user, const BufferId &bufferId) {
446 if(!isValidBuffer(user, bufferId))
449 QSqlQuery delBacklogQuery(logDb());
450 delBacklogQuery.prepare(queryString("delete_backlog_for_buffer"));
451 delBacklogQuery.bindValue(":bufferid", bufferId.toInt());
452 delBacklogQuery.exec();
453 if(!watchQuery(&delBacklogQuery))
456 QSqlQuery delBufferQuery(logDb());
457 delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
458 delBufferQuery.bindValue(":bufferid", bufferId.toInt());
459 delBufferQuery.exec();
460 if(!watchQuery(&delBufferQuery))
466 BufferId SqliteStorage::renameBuffer(const UserId &user, const NetworkId &networkId, const QString &newName, const QString &oldName) {
467 // check if such a buffer exists...
468 QSqlQuery existsQuery(logDb());
469 existsQuery.prepare(queryString("select_bufferByName"));
470 existsQuery.bindValue(":networkid", networkId.toInt());
471 existsQuery.bindValue(":userid", user.toInt());
472 existsQuery.bindValue(":buffercname", oldName.toLower());
474 if(!watchQuery(&existsQuery))
477 if(!existsQuery.first())
480 const int bufferid = existsQuery.value(0).toInt();
482 Q_ASSERT(!existsQuery.next());
484 // ... and if the new name is still free.
485 existsQuery.bindValue(":networkid", networkId.toInt());
486 existsQuery.bindValue(":userid", user.toInt());
487 existsQuery.bindValue(":buffercname", newName.toLower());
489 if(!watchQuery(&existsQuery))
492 if(existsQuery.first())
495 QSqlQuery renameBufferQuery(logDb());
496 renameBufferQuery.prepare(queryString("update_buffer_name"));
497 renameBufferQuery.bindValue(":buffername", newName);
498 renameBufferQuery.bindValue(":buffercname", newName.toLower());
499 renameBufferQuery.bindValue(":bufferid", bufferid);
500 renameBufferQuery.exec();
501 if(watchQuery(&existsQuery))
502 return BufferId(bufferid);
507 void SqliteStorage::setBufferLastSeen(UserId user, const BufferId &bufferId, const QDateTime &seenDate) {
508 QSqlQuery *query = cachedQuery("update_buffer_lastseen");
509 query->bindValue(":userid", user.toInt());
510 query->bindValue(":bufferid", bufferId.toInt());
511 query->bindValue(":lastseen", seenDate.toTime_t());
516 QHash<BufferId, QDateTime> SqliteStorage::bufferLastSeenDates(UserId user) {
517 QHash<BufferId, QDateTime> lastSeenHash;
518 QSqlQuery query(logDb());
519 query.prepare(queryString("select_buffer_lastseen_dates"));
520 query.bindValue(":userid", user.toInt());
522 if(!watchQuery(&query))
525 while(query.next()) {
526 lastSeenHash[query.value(0).toInt()] = QDateTime::fromTime_t(query.value(1).toUInt());
531 MsgId SqliteStorage::logMessage(Message msg) {
532 QSqlQuery *logMessageQuery = cachedQuery("insert_message");
533 logMessageQuery->bindValue(":time", msg.timestamp().toTime_t());
534 logMessageQuery->bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
535 logMessageQuery->bindValue(":type", msg.type());
536 logMessageQuery->bindValue(":flags", msg.flags());
537 logMessageQuery->bindValue(":sender", msg.sender());
538 logMessageQuery->bindValue(":message", msg.text());
539 logMessageQuery->exec();
541 if(logMessageQuery->lastError().isValid()) {
542 // constraint violation - must be NOT NULL constraint - probably the sender is missing...
543 if(logMessageQuery->lastError().number() == 19) {
544 QSqlQuery *addSenderQuery = cachedQuery("insert_sender");
545 addSenderQuery->bindValue(":sender", msg.sender());
546 addSenderQuery->exec();
547 watchQuery(addSenderQuery);
548 logMessageQuery->exec();
549 if(!watchQuery(logMessageQuery))
552 watchQuery(logMessageQuery);
556 MsgId msgId = logMessageQuery->lastInsertId().toInt();
557 Q_ASSERT(msgId.isValid());
561 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
562 QList<Message> messagelist;
563 // we have to determine the real offset first
564 QSqlQuery *offsetQuery = cachedQuery("select_messagesOffset");
565 offsetQuery->bindValue(":bufferid", buffer.bufferId().toInt());
566 offsetQuery->bindValue(":messageid", offset);
568 offsetQuery->first();
569 offset = offsetQuery->value(0).toInt();
571 // now let's select the messages
572 QSqlQuery *msgQuery = cachedQuery("select_messages");
573 msgQuery->bindValue(":bufferid", buffer.bufferId().toInt());
574 msgQuery->bindValue(":limit", lastmsgs);
575 msgQuery->bindValue(":offset", offset);
578 watchQuery(msgQuery);
580 while(msgQuery->next()) {
581 Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
583 (Message::Type)msgQuery->value(2).toUInt(),
584 msgQuery->value(5).toString(),
585 msgQuery->value(4).toString(),
586 msgQuery->value(3).toUInt());
587 msg.setMsgId(msgQuery->value(0).toInt());
594 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
595 QList<Message> messagelist;
596 // we have to determine the real offset first
597 QSqlQuery *offsetQuery = cachedQuery("select_messagesSinceOffset");
598 offsetQuery->bindValue(":bufferid", buffer.bufferId().toInt());
599 offsetQuery->bindValue(":since", since.toTime_t());
601 offsetQuery->first();
602 offset = offsetQuery->value(0).toInt();
604 // now let's select the messages
605 QSqlQuery *msgQuery = cachedQuery("select_messagesSince");
606 msgQuery->bindValue(":bufferid", buffer.bufferId().toInt());
607 msgQuery->bindValue(":since", since.toTime_t());
608 msgQuery->bindValue(":offset", offset);
611 watchQuery(msgQuery);
613 while(msgQuery->next()) {
614 Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
616 (Message::Type)msgQuery->value(2).toUInt(),
617 msgQuery->value(5).toString(),
618 msgQuery->value(4).toString(),
619 msgQuery->value(3).toUInt());
620 msg.setMsgId(msgQuery->value(0).toInt());
628 QList<Message> SqliteStorage::requestMsgRange(BufferInfo buffer, int first, int last) {
629 QList<Message> messagelist;
630 QSqlQuery *rangeQuery = cachedQuery("select_messageRange");
631 rangeQuery->bindValue(":bufferid", buffer.bufferId().toInt());
632 rangeQuery->bindValue(":firstmsg", first);
633 rangeQuery->bindValue(":lastmsg", last);
636 watchQuery(rangeQuery);
638 while(rangeQuery->next()) {
639 Message msg(QDateTime::fromTime_t(rangeQuery->value(1).toInt()),
641 (Message::Type)rangeQuery->value(2).toUInt(),
642 rangeQuery->value(5).toString(),
643 rangeQuery->value(4).toString(),
644 rangeQuery->value(3).toUInt());
645 msg.setMsgId(rangeQuery->value(0).toInt());
652 QString SqliteStorage::backlogFile() {
653 // kinda ugly, but I currently see no other way to do that
655 QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "\\quassel\\";
657 QString quasselDir = QDir::homePath() + "/.quassel/";
660 QDir qDir(quasselDir);
661 if(!qDir.exists(quasselDir))
662 qDir.mkpath(quasselDir);
664 return quasselDir + "quassel-storage.sqlite";