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