Renamed NetworkView[Widget] to BufferView[Widget].
[quassel.git] / core / sqlitestorage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by The Quassel 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) any later version.                                   *
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
24 SqliteStorage::SqliteStorage() {
25   // TODO I don't think that this path is failsafe for windows users :) 
26   QString backlogFile = Global::quasselDir + "/quassel-storage.sqlite";
27   logDb = QSqlDatabase::addDatabase("QSQLITE");
28   logDb.setDatabaseName(backlogFile);
29   bool ok = logDb.open();
30
31   if(!ok) {
32     qWarning(tr("Could not open backlog database: %1").arg(logDb.lastError().text()).toAscii());
33     qWarning(tr("Disabling logging...").toAscii());
34     Q_ASSERT(ok);
35     return;
36   }
37
38   // check if the db schema is up to date
39   QSqlQuery query = logDb.exec("SELECT MAX(version) FROM coreinfo");
40   if(query.first()) {
41     // TODO VersionCheck
42     //checkVersion(query.value(0));
43     qDebug() << "Sqlite is ready. Quassel Schema Version:" << query.value(0).toUInt();
44   } else {
45     initDb();
46   }
47
48   // we will need those pretty often... so let's speed things up:
49   createBufferQuery = new QSqlQuery(logDb);
50   createBufferQuery->prepare("INSERT INTO buffer (userid, networkid, buffername) VALUES (:userid, (SELECT networkid FROM network WHERE networkname = :networkname), :buffername)");
51
52   createNetworkQuery = new QSqlQuery(logDb);
53   createNetworkQuery->prepare("INSERT INTO network (userid, networkname) VALUES (:userid, :networkname)");
54
55   getBufferIdQuery = new QSqlQuery(logDb);
56   getBufferIdQuery->prepare("SELECT bufferid FROM buffer "
57                             "JOIN network ON buffer.networkid = network.networkid "
58                             "WHERE network.networkname = :networkname AND buffer.userid = :userid AND buffer.buffername = :buffername ");
59
60   logMessageQuery = new QSqlQuery(logDb);
61   logMessageQuery->prepare("INSERT INTO backlog (time, bufferid, type, flags, senderid, message) "
62                            "VALUES (:time, :bufferid, :type, :flags, (SELECT senderid FROM sender WHERE sender = :sender), :message)");
63
64   addSenderQuery = new QSqlQuery(logDb);
65   addSenderQuery->prepare("INSERT INTO sender (sender) VALUES (:sender)");
66
67   getLastMessageIdQuery = new QSqlQuery(logDb);
68   getLastMessageIdQuery->prepare("SELECT messageid FROM backlog "
69                                  "WHERE time = :time AND bufferid = :bufferid AND type = :type AND senderid = (SELECT senderid FROM sender WHERE sender = :sender)");
70
71   requestMsgsOffsetQuery = new QSqlQuery(logDb);
72   requestMsgsOffsetQuery->prepare("SELECT count(*) FROM backlog WHERE bufferid = :bufferid AND messageid < :messageid");
73
74   requestMsgsQuery = new QSqlQuery(logDb);
75   requestMsgsQuery->prepare("SELECT messageid, time,  type, flags, sender, message, displayname "
76                             "FROM backlog "
77                             "JOIN buffer ON backlog.bufferid = buffer.bufferid "
78                             "JOIN sender ON backlog.senderid = sender.senderid "
79                             "LEFT JOIN buffergroup ON buffer.groupid = buffergroup.groupid "
80                             "WHERE buffer.bufferid = :bufferid OR buffer.groupid = (SELECT groupid FROM buffer WHERE bufferid = :bufferid2) "
81                             "ORDER BY messageid DESC "
82                             "LIMIT :limit OFFSET :offset");
83
84   requestMsgsSinceOffsetQuery = new QSqlQuery(logDb);
85   requestMsgsSinceOffsetQuery->prepare("SELECT count(*) FROM backlog WHERE bufferid = :bufferid AND time >= :since");
86
87   requestMsgsSinceQuery = new QSqlQuery(logDb);
88   requestMsgsSinceQuery->prepare("SELECT messageid, time,  type, flags, sender, message, displayname "
89                                  "FROM backlog "
90                                  "JOIN buffer ON backlog.bufferid = buffer.bufferid "
91                                  "JOIN sender ON backlog.senderid = sender.senderid "
92                                  "LEFT JOIN buffergroup ON buffer.groupid = buffergroup.groupid "
93                                  "WHERE (buffer.bufferid = :bufferid OR buffer.groupid = (SELECT groupid FROM buffer WHERE bufferid = :bufferid2)) AND "
94                                  "backlog.time >= :since "
95                                  "ORDER BY messageid DESC "
96                                  "LIMIT -1 OFFSET :offset");
97
98   requestMsgRangeQuery = new QSqlQuery(logDb);
99   requestMsgRangeQuery->prepare("SELECT messageid, time,  type, flags, sender, message, displayname "
100                                 "FROM backlog "
101                                 "JOIN buffer ON backlog.bufferid = buffer.bufferid "
102                                 "JOIN sender ON backlog.senderid = sender.senderid "
103                                 "LEFT JOIN buffergroup ON buffer.groupid = buffergroup.groupid "
104                                 "WHERE (buffer.bufferid = :bufferid OR buffer.groupid = (SELECT groupid FROM buffer WHERE bufferid = :bufferid2)) AND "
105                                 "backlog.messageid >= :firstmsg AND backlog.messageid <= :lastmsg "
106                                 "ORDER BY messageid DESC ");
107
108 }
109
110 SqliteStorage::~SqliteStorage() {
111   delete logMessageQuery;
112   delete addSenderQuery;
113   delete getLastMessageIdQuery;
114   delete requestMsgsQuery;
115   delete requestMsgsOffsetQuery;
116   delete requestMsgsSinceQuery;
117   delete requestMsgsSinceOffsetQuery;
118   delete requestMsgRangeQuery;
119   delete createNetworkQuery;
120   delete createBufferQuery;
121   delete getBufferIdQuery;
122   logDb.close();
123 }
124
125
126 void SqliteStorage::initDb() {
127   logDb.exec("CREATE TABLE quasseluser ("
128              "userid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
129              "username TEXT UNIQUE NOT NULL,"
130              "password BLOB NOT NULL)");
131   
132   logDb.exec("CREATE TABLE sender ("
133              "senderid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
134              "sender TEXT UNIQUE NOT NULL)");
135   
136   logDb.exec("CREATE TABLE network ("
137              "networkid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
138              "userid INTEGER NOT NULL,"
139              "networkname TEXT NOT NULL,"
140              "UNIQUE (userid, networkname))");
141   
142   logDb.exec("CREATE TABLE buffergroup ("
143              "groupid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
144              "userid INTEGER NOT NULL,"
145              "displayname TEXT)");
146   
147   logDb.exec("CREATE TABLE buffer ("
148              "bufferid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
149              "userid INTEGER NOT NULL,"
150              "groupid INTEGER,"
151              "networkid INTEGER NOT NULL,"
152              "buffername TEXT NOT NULL)");
153   
154   logDb.exec("CREATE UNIQUE INDEX buffer_idx "
155              "ON buffer(userid, networkid, buffername)");
156     
157   logDb.exec("CREATE TABLE backlog ("
158              "messageid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
159              "time INTEGER NOT NULL,"
160              "bufferid INTEGER NOT NULL,"
161              "type INTEGER NOT NULL,"
162              "flags INTEGER NOT NULL,"
163              "senderid INTEGER NOT NULL,"
164              "message TEXT NOT NULL)");
165   
166   logDb.exec("CREATE TABLE coreinfo ("
167              "updateid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
168              "version INTEGER NOT NULL)");
169   
170   logDb.exec("INSERT INTO coreinfo (version) VALUES (0)");
171
172
173   // something fucked up -> no logging possible
174   // FIXME logDb.lastError is reset whenever exec is called
175   if(logDb.lastError().isValid()) { 
176     qWarning(tr("Could not create backlog table: %1").arg(logDb.lastError().text()).toAscii());
177     qWarning(tr("Disabling logging...").toAscii());
178     Q_ASSERT(false); // quassel does require logging
179   }
180 }
181
182 bool SqliteStorage::isAvailable() {
183   if(!QSqlDatabase::isDriverAvailable("QSQLITE")) return false;
184   return true;
185 }
186
187 QString SqliteStorage::displayName() {
188   // I think the class name is a good start here
189   return QString("SqliteStorage");
190 }
191
192 UserId SqliteStorage::addUser(QString user, QString password) {
193   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
194   cryptopass = cryptopass.toHex();
195
196   QSqlQuery query(logDb);
197   query.prepare("INSERT INTO quasseluser (username, password) VALUES (:username, :password)");
198   query.bindValue(":username", user);
199   query.bindValue(":password", cryptopass);
200   query.exec();
201   if(query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
202     return 0;
203   } 
204
205   query.prepare("SELECT userid FROM quasseluser WHERE username = :username");
206   query.bindValue(":username", user);
207   query.exec();
208   query.first();
209   return query.value(0).toUInt();
210 }
211
212 void SqliteStorage::updateUser(UserId user, QString password) {
213   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
214   cryptopass = cryptopass.toHex();
215
216   QSqlQuery query(logDb);
217   query.prepare("UPDATE quasseluser SET password = :password WHERE userid = :userid");
218   query.bindValue(":userid", user);
219   query.bindValue(":password", cryptopass);
220   query.exec();
221 }
222
223 UserId SqliteStorage::validateUser(QString user, QString password) {
224   QByteArray cryptopass = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1);
225   cryptopass = cryptopass.toHex();
226
227   QSqlQuery query(logDb);
228   query.prepare("SELECT userid FROM quasseluser WHERE username = :username AND password = :password");
229   query.bindValue(":username", user);
230   query.bindValue(":password", cryptopass);
231   query.exec();
232
233   if(query.first()) {
234     return query.value(0).toUInt();
235   } else {
236     return 0;
237   }
238 }
239
240 void SqliteStorage::delUser(UserId user) {
241   QSqlQuery query(logDb);
242   query.prepare("DELETE FROM backlog WHERE bufferid IN (SELECT DISTINCT bufferid FROM buffer WHERE userid = :userid");
243   query.bindValue(":userid", user);
244   query.exec();
245   query.prepare("DELETE FROM buffer WHERE userid = :userid");
246   query.bindValue(":userid", user);
247   query.exec();
248   query.prepare("DELETE FROM buffergroup WHERE userid = :userid");
249   query.bindValue(":userid", user);
250   query.exec();
251   query.prepare("DELETE FROM network WHERE userid = :userid");
252   query.bindValue(":userid", user);
253   query.exec();
254   query.prepare("DELETE FROM quasseluser WHERE userid = :userid");
255   query.bindValue(":userid", user);
256   query.exec();
257   // I hate the lack of foreign keys and on delete cascade... :(
258 }
259
260 void SqliteStorage::createBuffer(UserId user, QString network, QString buffer) {
261   createBufferQuery->bindValue(":userid", user);
262   createBufferQuery->bindValue(":networkname", network);
263   createBufferQuery->bindValue(":buffername", buffer);
264   createBufferQuery->exec();
265
266   if(createBufferQuery->lastError().isValid()) {
267     if(createBufferQuery->lastError().number() == 19) { // Null Constraint violation 
268       createNetworkQuery->bindValue(":userid", user);
269       createNetworkQuery->bindValue(":networkname", network);
270       createNetworkQuery->exec();
271       createBufferQuery->exec();
272       Q_ASSERT(!createNetworkQuery->lastError().isValid());
273       Q_ASSERT(!createBufferQuery->lastError().isValid());
274     } else {
275       // do panic!
276       qDebug() << "failed to create Buffer: ErrNo:" << createBufferQuery->lastError().number() << "ErrMsg:" << createBufferQuery->lastError().text();
277       Q_ASSERT(false);
278     }
279   }
280 }
281
282 BufferId SqliteStorage::getBufferId(UserId user, QString network, QString buffer) {
283   BufferId bufferid;
284   getBufferIdQuery->bindValue(":networkname", network);
285   getBufferIdQuery->bindValue(":userid", user);
286   getBufferIdQuery->bindValue(":buffername", buffer);
287   getBufferIdQuery->exec();
288
289   if(!getBufferIdQuery->first()) {
290     createBuffer(user, network, buffer);
291     getBufferIdQuery->exec();
292     if(getBufferIdQuery->first()) {
293       bufferid = BufferId(getBufferIdQuery->value(0).toUInt(), network, buffer);
294       emit bufferIdUpdated(bufferid);
295     }
296   } else {
297     bufferid = BufferId(getBufferIdQuery->value(0).toUInt(), network, buffer);
298   }
299
300   Q_ASSERT(!getBufferIdQuery->next());
301
302   return bufferid;
303 }
304
305 QList<BufferId> SqliteStorage::requestBuffers(UserId user, QDateTime since) {
306   QList<BufferId> bufferlist;
307   QSqlQuery query(logDb);
308   query.prepare("SELECT DISTINCT buffer.bufferid, networkname, buffername FROM buffer "
309                 "JOIN network ON buffer.networkid = network.networkid "
310                 "JOIN backlog ON buffer.bufferid = backlog.bufferid "
311                 "WHERE buffer.userid = :userid AND time >= :time");
312   query.bindValue(":userid", user);
313   if (since.isValid()) {
314     query.bindValue(":time", since.toTime_t());
315   } else {
316     query.bindValue(":time", 0);
317   }
318   
319   query.exec();
320
321   while(query.next()) {
322     bufferlist << BufferId(query.value(0).toUInt(), query.value(1).toString(), query.value(2).toString());
323   }
324   return bufferlist;
325 }
326
327 MsgId SqliteStorage::logMessage(Message msg) {
328   logMessageQuery->bindValue(":time", msg.timeStamp.toTime_t());
329   logMessageQuery->bindValue(":bufferid", msg.buffer.uid());
330   logMessageQuery->bindValue(":type", msg.type);
331   logMessageQuery->bindValue(":flags", msg.flags);
332   logMessageQuery->bindValue(":sender", msg.sender);
333   logMessageQuery->bindValue(":message", msg.text);
334   logMessageQuery->exec();
335   
336   if(logMessageQuery->lastError().isValid()) {
337     // constraint violation - must be NOT NULL constraint - probably the sender is missing...
338     if(logMessageQuery->lastError().number() == 19) { 
339       addSenderQuery->bindValue(":sender", msg.sender);
340       addSenderQuery->exec();
341       logMessageQuery->exec();
342       Q_ASSERT(!logMessageQuery->lastError().isValid());
343     } else {
344       qDebug() << "unhandled DB Error in logMessage(): Number:" << logMessageQuery->lastError().number() << "ErrMsg:" << logMessageQuery->lastError().text();
345     }
346   }
347
348   getLastMessageIdQuery->bindValue(":time", msg.timeStamp.toTime_t());
349   getLastMessageIdQuery->bindValue(":bufferid", msg.buffer.uid());
350   getLastMessageIdQuery->bindValue(":type", msg.type);
351   getLastMessageIdQuery->bindValue(":sender", msg.sender);
352   getLastMessageIdQuery->exec();
353
354   if(getLastMessageIdQuery->first()) {
355     return getLastMessageIdQuery->value(0).toUInt();
356   } else { // somethin went wrong... :(
357     qDebug() << getLastMessageIdQuery->lastQuery() << "time/bufferid/type/sender:" << msg.timeStamp.toTime_t() << msg.buffer.uid() << msg.type << msg.sender;
358     Q_ASSERT(false);
359     return 0;
360   }
361 }
362
363 QList<Message> SqliteStorage::requestMsgs(BufferId buffer, int lastmsgs, int offset) {
364   QList<Message> messagelist;
365   // we have to determine the real offset first
366   requestMsgsOffsetQuery->bindValue(":bufferid", buffer.uid());
367   requestMsgsOffsetQuery->bindValue(":messageid", offset);
368   requestMsgsOffsetQuery->exec();
369   requestMsgsOffsetQuery->first();
370   offset = requestMsgsOffsetQuery->value(0).toUInt();
371
372   // now let's select the messages
373   requestMsgsQuery->bindValue(":bufferid", buffer.uid());
374   requestMsgsQuery->bindValue(":bufferid2", buffer.uid());  // Qt can't handle the same placeholder used twice
375   requestMsgsQuery->bindValue(":limit", lastmsgs);
376   requestMsgsQuery->bindValue(":offset", offset);
377   requestMsgsQuery->exec();
378   while(requestMsgsQuery->next()) {
379     Message msg(QDateTime::fromTime_t(requestMsgsQuery->value(1).toInt()),
380                 buffer,
381                 (Message::Type)requestMsgsQuery->value(2).toUInt(),
382                 requestMsgsQuery->value(5).toString(),
383                 requestMsgsQuery->value(4).toString(),
384                 requestMsgsQuery->value(3).toUInt());
385     msg.msgId = requestMsgsQuery->value(0).toUInt();
386     messagelist << msg;
387   }
388
389   return messagelist;
390 }
391
392
393 QList<Message> SqliteStorage::requestMsgs(BufferId buffer, QDateTime since, int offset) {
394   QList<Message> messagelist;
395   // we have to determine the real offset first
396   requestMsgsSinceOffsetQuery->bindValue(":bufferid", buffer.uid());
397   requestMsgsSinceOffsetQuery->bindValue(":since", since.toTime_t());
398   requestMsgsSinceOffsetQuery->exec();
399   requestMsgsSinceOffsetQuery->first();
400   offset = requestMsgsSinceOffsetQuery->value(0).toUInt();  
401
402   // now let's select the messages
403   requestMsgsSinceQuery->bindValue(":bufferid", buffer.uid());
404   requestMsgsSinceQuery->bindValue(":bufferid2", buffer.uid());
405   requestMsgsSinceQuery->bindValue(":since", since.toTime_t());
406   requestMsgsSinceQuery->bindValue(":offset", offset);
407   requestMsgsSinceQuery->exec();
408
409   while(requestMsgsSinceQuery->next()) {
410     Message msg(QDateTime::fromTime_t(requestMsgsSinceQuery->value(1).toInt()),
411                 buffer,
412                 (Message::Type)requestMsgsSinceQuery->value(2).toUInt(),
413                 requestMsgsSinceQuery->value(5).toString(),
414                 requestMsgsSinceQuery->value(4).toString(),
415                 requestMsgsSinceQuery->value(3).toUInt());
416     msg.msgId = requestMsgsSinceQuery->value(0).toUInt();
417     messagelist << msg;
418   }
419
420   return messagelist;
421 }
422
423
424 QList<Message> SqliteStorage::requestMsgRange(BufferId buffer, int first, int last) {
425   QList<Message> messagelist;
426   requestMsgRangeQuery->bindValue(":bufferid", buffer.uid());
427   requestMsgRangeQuery->bindValue(":bufferid2", buffer.uid());
428   requestMsgRangeQuery->bindValue(":firstmsg", first);
429   requestMsgRangeQuery->bindValue(":lastmsg", last);
430
431   while(requestMsgRangeQuery->next()) {
432     Message msg(QDateTime::fromTime_t(requestMsgRangeQuery->value(1).toInt()),
433                 buffer,
434                 (Message::Type)requestMsgRangeQuery->value(2).toUInt(),
435                 requestMsgRangeQuery->value(5).toString(),
436                 requestMsgRangeQuery->value(4).toString(),
437                 requestMsgRangeQuery->value(3).toUInt());
438     msg.msgId = requestMsgRangeQuery->value(0).toUInt();
439     messagelist << msg;
440   }
441
442   return messagelist;
443 }
444
445 void SqliteStorage::importOldBacklog() {
446   QSqlQuery query(logDb);
447   int user;
448   query.prepare("SELECT MIN(userid) FROM quasseluser");
449   query.exec();
450   if(!query.first()) {
451     qDebug() << "create a user first!";
452   } else {
453     user = query.value(0).toUInt();
454   }
455   query.prepare("DELETE FROM backlog WHERE bufferid IN (SELECT DISTINCT bufferid FROM buffer WHERE userid = :userid");
456   query.bindValue(":userid", user);
457   query.exec();
458   query.prepare("DELETE FROM buffer WHERE userid = :userid");
459   query.bindValue(":userid", user);
460   query.exec();
461   query.prepare("DELETE FROM buffergroup WHERE userid = :userid");
462   query.bindValue(":userid", user);
463   query.exec();
464   query.prepare("DELETE FROM network WHERE userid = :userid");
465   query.bindValue(":userid", user);
466   query.exec();
467   logDb.commit();
468   qDebug() << "All userdata has been deleted";
469   qDebug() << "importing old backlog files...";
470   initBackLogOld(user);
471   logDb.commit();
472   return;
473 }
474
475
476