Code cleanup. Distclean advised
[quassel.git] / src / core / sqlitestorage.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 "sqlitestorage.h"
22
23 #include <QCryptographicHash>
24 #include <QtSql>
25
26 #include "network.h"
27
28 SqliteStorage::SqliteStorage(QObject *parent)
29   : AbstractSqlStorage(parent)
30 {
31 }
32
33 SqliteStorage::~SqliteStorage() {
34 }
35
36 bool SqliteStorage::isAvailable() {
37   if(!QSqlDatabase::isDriverAvailable("QSQLITE")) return false;
38   return true;
39 }
40
41 QString SqliteStorage::displayName() {
42   return QString("SQLite");
43 }
44
45 QString SqliteStorage::engineName() {
46   return SqliteStorage::displayName();
47 }
48
49 int SqliteStorage::installedSchemaVersion() {
50   QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
51   if(query.first())
52     return query.value(0).toInt();
53
54   // maybe it's really old... (schema version 0)
55   query = logDb().exec("SELECT MAX(version) FROM coreinfo");
56   if(query.first())
57     return query.value(0).toInt();
58
59   return AbstractSqlStorage::installedSchemaVersion();
60 }
61
62 UserId SqliteStorage::addUser(const QString &user, const QString &password) {
63   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
64   cryptopass = cryptopass.toHex();
65
66   QSqlQuery query(logDb());
67   query.prepare(queryString("insert_quasseluser"));
68   query.bindValue(":username", user);
69   query.bindValue(":password", cryptopass);
70   query.exec();
71   if(query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
72     return 0;
73   }
74
75   query.prepare(queryString("select_userid"));
76   query.bindValue(":username", user);
77   query.exec();
78   query.first();
79   UserId uid = query.value(0).toInt();
80   emit userAdded(uid, user);
81   return uid;
82 }
83
84 void SqliteStorage::updateUser(UserId user, const QString &password) {
85   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
86   cryptopass = cryptopass.toHex();
87
88   QSqlQuery query(logDb());
89   query.prepare(queryString("update_userpassword"));
90   query.bindValue(":userid", user.toInt());
91   query.bindValue(":password", cryptopass);
92   query.exec();
93 }
94
95 void SqliteStorage::renameUser(UserId user, const QString &newName) {
96   QSqlQuery query(logDb());
97   query.prepare(queryString("update_username"));
98   query.bindValue(":userid", user.toInt());
99   query.bindValue(":username", newName);
100   query.exec();
101   emit userRenamed(user, newName);
102 }
103
104 UserId SqliteStorage::validateUser(const QString &user, const QString &password) {
105   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
106   cryptopass = cryptopass.toHex();
107
108   QSqlQuery query(logDb());
109   query.prepare(queryString("select_authuser"));
110   query.bindValue(":username", user);
111   query.bindValue(":password", cryptopass);
112   query.exec();
113
114   if(query.first()) {
115     return query.value(0).toInt();
116   } else {
117     return 0;
118   }
119 }
120
121 void SqliteStorage::delUser(UserId user) {
122   QSqlQuery query(logDb());
123   query.prepare(queryString("delete_backlog_by_uid"));
124   query.bindValue(":userid", user.toInt());
125   query.exec();
126   
127   query.prepare(queryString("delete_buffers_by_uid"));
128   query.bindValue(":userid", user.toInt());
129   query.exec();
130   
131   query.prepare(queryString("delete_networks_by_uid"));
132   query.bindValue(":userid", user.toInt());
133   query.exec();
134   
135   query.prepare(queryString("delete_quasseluser"));
136   query.bindValue(":userid", user.toInt());
137   query.exec();
138   // I hate the lack of foreign keys and on delete cascade... :(
139   emit userRemoved(user);
140 }
141
142 NetworkId SqliteStorage::createNetworkId(UserId user, const NetworkInfo &info) {
143   NetworkId networkId;
144   QSqlQuery query(logDb());
145   query.prepare(queryString("insert_network"));
146   query.bindValue(":userid", user.toInt());
147   query.bindValue(":networkname", info.networkName);
148   query.exec();
149   
150   networkId = getNetworkId(user, info.networkName);
151   if(!networkId.isValid()) {
152     watchQuery(&query);
153   }
154   return networkId;
155 }
156
157 NetworkId SqliteStorage::getNetworkId(UserId user, const QString &network) {
158   QSqlQuery query(logDb());
159   query.prepare("SELECT networkid FROM network "
160                 "WHERE userid = :userid AND networkname = :networkname");
161   query.bindValue(":userid", user.toInt());
162   query.bindValue(":networkname", network);
163   query.exec();
164   
165   if(query.first())
166     return query.value(0).toInt();
167   else
168     return NetworkId();
169 }
170
171 void SqliteStorage::createBuffer(UserId user, const NetworkId &networkId, const QString &buffer) {
172   QSqlQuery *query = cachedQuery("insert_buffer");
173   query->bindValue(":userid", user.toInt());
174   query->bindValue(":networkid", networkId.toInt());
175   query->bindValue(":buffername", buffer);
176   query->exec();
177
178   watchQuery(query);
179 }
180
181 BufferInfo SqliteStorage::getBufferInfo(UserId user, const NetworkId &networkId, const QString &buffer) {
182   QSqlQuery *query = cachedQuery("select_bufferByName");
183   query->bindValue(":networkid", networkId.toInt());
184   query->bindValue(":userid", user.toInt());
185   query->bindValue(":buffername", buffer);
186   query->exec();
187
188   if(!query->first()) {
189     createBuffer(user, networkId, buffer);
190     query->exec();
191     if(!query->first()) {
192       watchQuery(query);
193       qWarning() << "unable to create BufferInfo for:" << user << networkId << buffer;
194       return BufferInfo();
195     }
196   }
197
198   BufferInfo bufferInfo = BufferInfo(query->value(0).toInt(), networkId, 0, buffer);
199   if(query->next()) {
200     qWarning() << "SqliteStorage::getBufferInfo(): received more then one Buffer!";
201     qWarning() << "         Query:" << query->lastQuery();
202     qWarning() << "  bound Values:" << query->boundValues();
203     Q_ASSERT(false);
204   }
205
206   return bufferInfo;
207 }
208
209 QList<BufferInfo> SqliteStorage::requestBuffers(UserId user, QDateTime since) {
210   uint time = 0;
211   if(since.isValid())
212     time = since.toTime_t();
213   
214   QList<BufferInfo> bufferlist;
215   QSqlQuery query(logDb());
216   query.prepare(queryString("select_buffers"));
217   query.bindValue(":userid", user.toInt());
218   query.bindValue(":time", time);
219   
220   query.exec();
221   watchQuery(&query);
222   while(query.next()) {
223     bufferlist << BufferInfo(query.value(0).toInt(), query.value(2).toInt(), 0, query.value(1).toString());
224   }
225   return bufferlist;
226 }
227
228 MsgId SqliteStorage::logMessage(Message msg) {
229   QSqlQuery *logMessageQuery = cachedQuery("insert_message");
230   logMessageQuery->bindValue(":time", msg.timestamp().toTime_t());
231   logMessageQuery->bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
232   logMessageQuery->bindValue(":type", msg.type());
233   logMessageQuery->bindValue(":flags", msg.flags());
234   logMessageQuery->bindValue(":sender", msg.sender());
235   logMessageQuery->bindValue(":message", msg.text());
236   logMessageQuery->exec();
237   
238   if(logMessageQuery->lastError().isValid()) {
239     // constraint violation - must be NOT NULL constraint - probably the sender is missing...
240     if(logMessageQuery->lastError().number() == 19) {
241       QSqlQuery *addSenderQuery = cachedQuery("insert_sender");
242       addSenderQuery->bindValue(":sender", msg.sender());
243       addSenderQuery->exec();
244       watchQuery(addSenderQuery);
245       logMessageQuery->exec();
246       if(!watchQuery(logMessageQuery))
247         return 0;
248     } else {
249       watchQuery(logMessageQuery);
250     }
251   }
252
253   QSqlQuery *getLastMessageIdQuery = cachedQuery("select_lastMessage");
254   getLastMessageIdQuery->bindValue(":time", msg.timestamp().toTime_t());
255   getLastMessageIdQuery->bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
256   getLastMessageIdQuery->bindValue(":type", msg.type());
257   getLastMessageIdQuery->bindValue(":sender", msg.sender());
258   getLastMessageIdQuery->exec();
259
260   if(getLastMessageIdQuery->first()) {
261     return getLastMessageIdQuery->value(0).toInt();
262   } else { // somethin went wrong... :(
263     qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timestamp().toTime_t() << msg.bufferInfo().bufferId() << msg.type() << msg.sender();
264     Q_ASSERT(false);
265     return 0;
266   }
267 }
268
269 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
270   QList<Message> messagelist;
271   // we have to determine the real offset first
272   QSqlQuery *offsetQuery = cachedQuery("select_messagesOffset");
273   offsetQuery->bindValue(":bufferid", buffer.bufferId().toInt());
274   offsetQuery->bindValue(":messageid", offset);
275   offsetQuery->exec();
276   offsetQuery->first();
277   offset = offsetQuery->value(0).toInt();
278
279   // now let's select the messages
280   QSqlQuery *msgQuery = cachedQuery("select_messages");
281   msgQuery->bindValue(":bufferid", buffer.bufferId().toInt());
282   msgQuery->bindValue(":limit", lastmsgs);
283   msgQuery->bindValue(":offset", offset);
284   msgQuery->exec();
285   
286   watchQuery(msgQuery);
287   
288   while(msgQuery->next()) {
289     Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
290                 buffer,
291                 (Message::Type)msgQuery->value(2).toUInt(),
292                 msgQuery->value(5).toString(),
293                 msgQuery->value(4).toString(),
294                 msgQuery->value(3).toUInt());
295     msg.setMsgId(msgQuery->value(0).toInt());
296     messagelist << msg;
297   }
298   return messagelist;
299 }
300
301
302 QList<Message> SqliteStorage::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
303   QList<Message> messagelist;
304   // we have to determine the real offset first
305   QSqlQuery *offsetQuery = cachedQuery("select_messagesSinceOffset");
306   offsetQuery->bindValue(":bufferid", buffer.bufferId().toInt());
307   offsetQuery->bindValue(":since", since.toTime_t());
308   offsetQuery->exec();
309   offsetQuery->first();
310   offset = offsetQuery->value(0).toInt();
311
312   // now let's select the messages
313   QSqlQuery *msgQuery = cachedQuery("select_messagesSince");
314   msgQuery->bindValue(":bufferid", buffer.bufferId().toInt());
315   msgQuery->bindValue(":since", since.toTime_t());
316   msgQuery->bindValue(":offset", offset);
317   msgQuery->exec();
318
319   watchQuery(msgQuery);
320   
321   while(msgQuery->next()) {
322     Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
323                 buffer,
324                 (Message::Type)msgQuery->value(2).toUInt(),
325                 msgQuery->value(5).toString(),
326                 msgQuery->value(4).toString(),
327                 msgQuery->value(3).toUInt());
328     msg.setMsgId(msgQuery->value(0).toInt());
329     messagelist << msg;
330   }
331
332   return messagelist;
333 }
334
335
336 QList<Message> SqliteStorage::requestMsgRange(BufferInfo buffer, int first, int last) {
337   QList<Message> messagelist;
338   QSqlQuery *rangeQuery = cachedQuery("select_messageRange");
339   rangeQuery->bindValue(":bufferid", buffer.bufferId().toInt());
340   rangeQuery->bindValue(":firstmsg", first);
341   rangeQuery->bindValue(":lastmsg", last);
342   rangeQuery->exec();
343
344   watchQuery(rangeQuery);
345   
346   while(rangeQuery->next()) {
347     Message msg(QDateTime::fromTime_t(rangeQuery->value(1).toInt()),
348                 buffer,
349                 (Message::Type)rangeQuery->value(2).toUInt(),
350                 rangeQuery->value(5).toString(),
351                 rangeQuery->value(4).toString(),
352                 rangeQuery->value(3).toUInt());
353     msg.setMsgId(rangeQuery->value(0).toInt());
354     messagelist << msg;
355   }
356
357   return messagelist;
358 }
359
360 QString SqliteStorage::backlogFile() {
361   // kinda ugly, but I currently see no other way to do that
362 #ifdef Q_OS_WIN32
363   QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "\\quassel\\";
364 #else
365   QString quasselDir = QDir::homePath() + "/.quassel/";
366 #endif
367
368   QDir qDir(quasselDir);
369   if(!qDir.exists(quasselDir))
370     qDir.mkpath(quasselDir);
371   
372   return quasselDir + "quassel-storage.sqlite";  
373 }
374