Monolithic build features now zero setup configuration: click and run
[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 <QtSql>
24
25 #include "network.h"
26
27 #include "util.h"
28 #include "logger.h"
29
30 SqliteStorage::SqliteStorage(QObject *parent)
31   : AbstractSqlStorage(parent)
32 {
33 }
34
35 SqliteStorage::~SqliteStorage() {
36 }
37
38 bool SqliteStorage::isAvailable() const {
39   if(!QSqlDatabase::isDriverAvailable("QSQLITE")) return false;
40   return true;
41 }
42
43 QString SqliteStorage::displayName() const {
44   return QString("SQLite");
45 }
46
47 QString SqliteStorage::description() const {
48   return tr("SQLite is a file-based database engine that does not require any setup. It is suitable for small and medium-sized "
49             "databases that do not require access via network. Use SQLite if your Quassel Core should store its data on the same machine "
50             "it is running on, and if you only expect a few users to use your core.");
51 }
52
53 int SqliteStorage::installedSchemaVersion() {
54   QSqlQuery query = logDb().exec("SELECT value FROM coreinfo WHERE key = 'schemaversion'");
55   if(query.first())
56     return query.value(0).toInt();
57
58   // maybe it's really old... (schema version 0)
59   query = logDb().exec("SELECT MAX(version) FROM coreinfo");
60   if(query.first())
61     return query.value(0).toInt();
62
63   return AbstractSqlStorage::installedSchemaVersion();
64 }
65
66 UserId SqliteStorage::addUser(const QString &user, const QString &password) {
67   QSqlQuery query(logDb());
68   query.prepare(queryString("insert_quasseluser"));
69   query.bindValue(":username", user);
70   query.bindValue(":password", cryptedPassword(password));
71   query.exec();
72   if(query.lastError().isValid() && query.lastError().number() == 19) { // user already exists - sadly 19 seems to be the general constraint violation error...
73     return 0;
74   }
75
76   query.prepare(queryString("select_userid"));
77   query.bindValue(":username", user);
78   query.exec();
79   query.first();
80   UserId uid = query.value(0).toInt();
81   emit userAdded(uid, user);
82   return uid;
83 }
84
85 void SqliteStorage::updateUser(UserId user, const QString &password) {
86   QSqlQuery query(logDb());
87   query.prepare(queryString("update_userpassword"));
88   query.bindValue(":userid", user.toInt());
89   query.bindValue(":password", cryptedPassword(password));
90   query.exec();
91 }
92
93 void SqliteStorage::renameUser(UserId user, const QString &newName) {
94   QSqlQuery query(logDb());
95   query.prepare(queryString("update_username"));
96   query.bindValue(":userid", user.toInt());
97   query.bindValue(":username", newName);
98   query.exec();
99   emit userRenamed(user, newName);
100 }
101
102 UserId SqliteStorage::validateUser(const QString &user, const QString &password) {
103   QSqlQuery query(logDb());
104   query.prepare(queryString("select_authuser"));
105   query.bindValue(":username", user);
106   query.bindValue(":password", cryptedPassword(password));
107   query.exec();
108
109   if(query.first()) {
110     return query.value(0).toInt();
111   } else {
112     return 0;
113   }
114 }
115
116 void SqliteStorage::delUser(UserId user) {
117   QSqlQuery query(logDb());
118   query.prepare(queryString("delete_backlog_by_uid"));
119   query.bindValue(":userid", user.toInt());
120   query.exec();
121   
122   query.prepare(queryString("delete_buffers_by_uid"));
123   query.bindValue(":userid", user.toInt());
124   query.exec();
125   
126   query.prepare(queryString("delete_networks_by_uid"));
127   query.bindValue(":userid", user.toInt());
128   query.exec();
129   
130   query.prepare(queryString("delete_quasseluser"));
131   query.bindValue(":userid", user.toInt());
132   query.exec();
133   // I hate the lack of foreign keys and on delete cascade... :(
134   emit userRemoved(user);
135 }
136
137 void SqliteStorage::setUserSetting(UserId userId, const QString &settingName, const QVariant &data) {
138   QByteArray rawData;
139   QDataStream out(&rawData, QIODevice::WriteOnly);
140   out.setVersion(QDataStream::Qt_4_2);
141   out << data;
142
143   QSqlQuery query(logDb());
144   query.prepare(queryString("insert_user_setting"));
145   query.bindValue(":userid", userId.toInt());
146   query.bindValue(":settingname", settingName);
147   query.bindValue(":settingvalue", rawData);
148   query.exec();
149
150   if(query.lastError().isValid()) {
151     QSqlQuery updateQuery(logDb());
152     updateQuery.prepare(queryString("update_user_setting"));
153     updateQuery.bindValue(":userid", userId.toInt());
154     updateQuery.bindValue(":settingname", settingName);
155     updateQuery.bindValue(":settingvalue", rawData);
156     updateQuery.exec();
157   }
158                   
159 }
160
161 QVariant SqliteStorage::getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData) {
162   QSqlQuery query(logDb());
163   query.prepare(queryString("select_user_setting"));
164   query.bindValue(":userid", userId.toInt());
165   query.bindValue(":settingname", settingName);
166   query.exec();
167
168   if(query.first()) {
169     QVariant data;
170     QByteArray rawData = query.value(0).toByteArray();
171     QDataStream in(&rawData, QIODevice::ReadOnly);
172     in.setVersion(QDataStream::Qt_4_2);
173     in >> data;
174     return data;
175   } else {
176     return defaultData;
177   }
178 }
179
180 NetworkId SqliteStorage::createNetwork(UserId user, const NetworkInfo &info) {
181   NetworkId networkId;
182   QSqlQuery query(logDb());
183   query.prepare(queryString("insert_network"));
184   query.bindValue(":userid", user.toInt());
185   query.bindValue(":networkname", info.networkName);
186   query.exec();
187   
188   networkId = getNetworkId(user, info.networkName);
189   if(!networkId.isValid()) {
190     watchQuery(&query);
191   } else {
192     NetworkInfo newNetworkInfo = info;
193     newNetworkInfo.networkId = networkId;
194     updateNetwork(user, newNetworkInfo);
195   }
196   return networkId;
197 }
198
199 bool SqliteStorage::updateNetwork(UserId user, const NetworkInfo &info) {
200   if(!isValidNetwork(user, info.networkId))
201      return false;
202   
203   QSqlQuery updateQuery(logDb());
204   updateQuery.prepare(queryString("update_network"));
205   updateQuery.bindValue(":networkname", info.networkName);
206   updateQuery.bindValue(":identityid", info.identity.toInt());
207   updateQuery.bindValue(":usecustomencoding", info.useCustomEncodings ? 1 : 0);
208   updateQuery.bindValue(":encodingcodec", QString(info.codecForEncoding));
209   updateQuery.bindValue(":decodingcodec", QString(info.codecForDecoding));
210   updateQuery.bindValue(":servercodec", QString(info.codecForServer));
211   updateQuery.bindValue(":userandomserver", info.useRandomServer ? 1 : 0);
212   updateQuery.bindValue(":perform", info.perform.join("\n"));
213   updateQuery.bindValue(":useautoidentify", info.useAutoIdentify ? 1 : 0);
214   updateQuery.bindValue(":autoidentifyservice", info.autoIdentifyService);
215   updateQuery.bindValue(":autoidentifypassword", info.autoIdentifyPassword);
216   updateQuery.bindValue(":useautoreconnect", info.useAutoReconnect ? 1 : 0);
217   updateQuery.bindValue(":autoreconnectinterval", info.autoReconnectInterval);
218   updateQuery.bindValue(":autoreconnectretries", info.autoReconnectRetries);
219   updateQuery.bindValue(":unlimitedconnectretries", info.unlimitedReconnectRetries ? 1 : 0);
220   updateQuery.bindValue(":rejoinchannels", info.rejoinChannels ? 1 : 0);
221   updateQuery.bindValue(":networkid", info.networkId.toInt());
222   updateQuery.exec();
223   if(!watchQuery(&updateQuery))
224     return false;
225
226   QSqlQuery dropServersQuery(logDb());
227   dropServersQuery.prepare("DELETE FROM ircserver WHERE networkid = :networkid");
228   dropServersQuery.bindValue(":networkid", info.networkId.toInt());
229   dropServersQuery.exec();
230   if(!watchQuery(&dropServersQuery))
231     return false;
232
233   QSqlQuery insertServersQuery(logDb());
234   insertServersQuery.prepare(queryString("insert_server"));
235   foreach(QVariant server_, info.serverList) {
236     QVariantMap server = server_.toMap();
237     insertServersQuery.bindValue(":hostname", server["Host"]);
238     insertServersQuery.bindValue(":port", server["Port"].toInt());
239     insertServersQuery.bindValue(":password", server["Password"]);
240     insertServersQuery.bindValue(":ssl", server["UseSSL"].toBool() ? 1 : 0);
241     insertServersQuery.bindValue(":userid", user.toInt());
242     insertServersQuery.bindValue(":networkid", info.networkId.toInt());
243
244     insertServersQuery.exec();
245     if(!watchQuery(&insertServersQuery))
246       return false;
247   }
248   
249   return true;
250 }
251
252 bool SqliteStorage::removeNetwork(UserId user, const NetworkId &networkId) {
253   if(!isValidNetwork(user, networkId))
254      return false;
255
256   bool withTransaction = logDb().driver()->hasFeature(QSqlDriver::Transactions);
257   if(withTransaction) {
258     sync();
259     if(!logDb().transaction()) {
260       quWarning() << "SqliteStorage::removeNetwork(): cannot start transaction. continuing with out rollback support!";
261       withTransaction = false;
262     }
263   }
264   
265   QSqlQuery deleteBacklogQuery(logDb());
266   deleteBacklogQuery.prepare(queryString("delete_backlog_for_network"));
267   deleteBacklogQuery.bindValue(":networkid", networkId.toInt());
268   deleteBacklogQuery.exec();
269   if(!watchQuery(&deleteBacklogQuery)) {
270     if(withTransaction)
271       logDb().rollback();
272     return false;
273   }
274   
275   QSqlQuery deleteBuffersQuery(logDb());
276   deleteBuffersQuery.prepare(queryString("delete_buffers_for_network"));
277   deleteBuffersQuery.bindValue(":networkid", networkId.toInt());
278   deleteBuffersQuery.exec();
279   if(!watchQuery(&deleteBuffersQuery)) {
280     if(withTransaction)
281       logDb().rollback();
282     return false;
283   }
284   
285   QSqlQuery deleteServersQuery(logDb());
286   deleteServersQuery.prepare(queryString("delete_ircservers_for_network"));
287   deleteServersQuery.bindValue(":networkid", networkId.toInt());
288   deleteServersQuery.exec();
289   if(!watchQuery(&deleteServersQuery)) {
290     if(withTransaction)
291       logDb().rollback();
292     return false;
293   }
294   
295   QSqlQuery deleteNetworkQuery(logDb());
296   deleteNetworkQuery.prepare(queryString("delete_network"));
297   deleteNetworkQuery.bindValue(":networkid", networkId.toInt());
298   deleteNetworkQuery.exec();
299   if(!watchQuery(&deleteNetworkQuery)) {
300     if(withTransaction)
301       logDb().rollback();
302     return false;
303   }
304
305   logDb().commit();
306   return true;
307 }
308
309 QList<NetworkInfo> SqliteStorage::networks(UserId user) {
310   QList<NetworkInfo> nets;
311
312   QSqlQuery networksQuery(logDb());
313   networksQuery.prepare(queryString("select_networks_for_user"));
314   networksQuery.bindValue(":userid", user.toInt());
315   
316   QSqlQuery serversQuery(logDb());
317   serversQuery.prepare(queryString("select_servers_for_network"));
318
319   networksQuery.exec();
320   if(!watchQuery(&networksQuery))
321     return nets;
322
323   while(networksQuery.next()) {
324     NetworkInfo net;
325     net.networkId = networksQuery.value(0).toInt();
326     net.networkName = networksQuery.value(1).toString();
327     net.identity = networksQuery.value(2).toInt();
328     net.codecForServer = networksQuery.value(3).toString().toAscii();
329     net.codecForEncoding = networksQuery.value(4).toString().toAscii();
330     net.codecForDecoding = networksQuery.value(5).toString().toAscii();
331     net.useRandomServer = networksQuery.value(6).toInt() == 1 ? true : false;
332     net.perform = networksQuery.value(7).toString().split("\n");
333     net.useAutoIdentify = networksQuery.value(8).toInt() == 1 ? true : false;
334     net.autoIdentifyService = networksQuery.value(9).toString();
335     net.autoIdentifyPassword = networksQuery.value(10).toString();
336     net.useAutoReconnect = networksQuery.value(11).toInt() == 1 ? true : false;
337     net.autoReconnectInterval = networksQuery.value(12).toUInt();
338     net.autoReconnectRetries = networksQuery.value(13).toInt();
339     net.unlimitedReconnectRetries = networksQuery.value(14).toInt() == 1 ? true : false;
340     net.rejoinChannels = networksQuery.value(15).toInt() == 1 ? true : false;
341
342     serversQuery.bindValue(":networkid", net.networkId.toInt());
343     serversQuery.exec();
344     if(!watchQuery(&serversQuery))
345       return nets;
346
347     QVariantList servers;
348     while(serversQuery.next()) {
349       QVariantMap server;
350       server["Host"] = serversQuery.value(0).toString();
351       server["Port"] = serversQuery.value(1).toInt();
352       server["Password"] = serversQuery.value(2).toString();
353       server["UseSSL"] = serversQuery.value(3).toInt() == 1 ? true : false;
354       servers << server;
355     }
356     net.serverList = servers;
357     nets << net;
358   }
359   return nets;
360 }
361
362 bool SqliteStorage::isValidNetwork(UserId user, const NetworkId &networkId) {
363   QSqlQuery query(logDb());
364   query.prepare(queryString("select_networkExists"));
365   query.bindValue(":userid", user.toInt());
366   query.bindValue(":networkid", networkId.toInt());
367   query.exec();
368
369   watchQuery(&query); // there should not occur any errors
370   if(!query.first())
371     return false;
372   
373   Q_ASSERT(!query.next());
374   return true;
375 }
376
377 bool SqliteStorage::isValidBuffer(const UserId &user, const BufferId &bufferId) {
378   QSqlQuery query(logDb());
379   query.prepare(queryString("select_bufferExists"));
380   query.bindValue(":userid", user.toInt());
381   query.bindValue(":bufferid", bufferId.toInt());
382   query.exec();
383
384   watchQuery(&query);
385   if(!query.first())
386     return false;
387
388   Q_ASSERT(!query.next());
389   return true;
390 }
391
392 NetworkId SqliteStorage::getNetworkId(UserId user, const QString &network) {
393   QSqlQuery query(logDb());
394   query.prepare("SELECT networkid FROM network "
395                 "WHERE userid = :userid AND networkname = :networkname");
396   query.bindValue(":userid", user.toInt());
397   query.bindValue(":networkname", network);
398   query.exec();
399   
400   if(query.first())
401     return query.value(0).toInt();
402   else
403     return NetworkId();
404 }
405
406 QList<NetworkId> SqliteStorage::connectedNetworks(UserId user) {
407   QList<NetworkId> connectedNets;
408   QSqlQuery query(logDb());
409   query.prepare(queryString("select_connected_networks"));
410   query.bindValue(":userid", user.toInt());
411   query.exec();
412   watchQuery(&query);
413
414   while(query.next()) {
415     connectedNets << query.value(0).toInt();
416   }
417   
418   return connectedNets;
419 }
420
421 void SqliteStorage::setNetworkConnected(UserId user, const NetworkId &networkId, bool isConnected) {
422   QSqlQuery query(logDb());
423   query.prepare(queryString("update_network_connected"));
424   query.bindValue(":userid", user.toInt());
425   query.bindValue(":networkid", networkId.toInt());
426   query.bindValue(":connected", isConnected ? 1 : 0);
427   query.exec();
428   watchQuery(&query);
429 }
430
431 QHash<QString, QString> SqliteStorage::persistentChannels(UserId user, const NetworkId &networkId) {
432   QHash<QString, QString> persistentChans;
433   QSqlQuery query(logDb());
434   query.prepare(queryString("select_persistent_channels"));
435   query.bindValue(":userid", user.toInt());
436   query.bindValue(":networkid", networkId.toInt());
437   query.exec();
438   watchQuery(&query);
439
440   while(query.next()) {
441     persistentChans[query.value(0).toString()] = query.value(1).toString();
442   }
443   
444   return persistentChans;
445 }
446
447 void SqliteStorage::setChannelPersistent(UserId user, const NetworkId &networkId, const QString &channel, bool isJoined) {
448   QSqlQuery query(logDb());
449   query.prepare(queryString("update_buffer_persistent_channel"));
450   query.bindValue(":userid", user.toInt());
451   query.bindValue(":networkId", networkId.toInt());
452   query.bindValue(":buffercname", channel.toLower());
453   query.bindValue(":joined", isJoined ? 1 : 0);
454   query.exec();
455   watchQuery(&query);
456 }
457
458 void SqliteStorage::setPersistentChannelKey(UserId user, const NetworkId &networkId, const QString &channel, const QString &key) {
459   QSqlQuery query(logDb());
460   query.prepare(queryString("update_buffer_set_channel_key"));
461   query.bindValue(":userid", user.toInt());
462   query.bindValue(":networkId", networkId.toInt());
463   query.bindValue(":buffercname", channel.toLower());
464   query.bindValue(":key", key);
465   query.exec();
466   watchQuery(&query);
467 }
468
469
470 void SqliteStorage::createBuffer(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer) {
471   QSqlQuery *query = cachedQuery("insert_buffer");
472   query->bindValue(":userid", user.toInt());
473   query->bindValue(":networkid", networkId.toInt());
474   query->bindValue(":buffertype", (int)type);
475   query->bindValue(":buffername", buffer);
476   query->bindValue(":buffercname", buffer.toLower());
477   query->exec();
478
479   watchQuery(query);
480 }
481
482 BufferInfo SqliteStorage::getBufferInfo(UserId user, const NetworkId &networkId, BufferInfo::Type type, const QString &buffer) {
483   QSqlQuery *query = cachedQuery("select_bufferByName");
484   query->bindValue(":networkid", networkId.toInt());
485   query->bindValue(":userid", user.toInt());
486   query->bindValue(":buffercname", buffer.toLower());
487   query->exec();
488
489   if(!query->first()) {
490     createBuffer(user, networkId, type, buffer);
491     query->exec();
492     if(!query->first()) {
493       watchQuery(query);
494       quWarning() << "unable to create BufferInfo for:" << user << networkId << buffer;
495       return BufferInfo();
496     }
497   }
498
499   BufferInfo bufferInfo = BufferInfo(query->value(0).toInt(), networkId, (BufferInfo::Type)query->value(1).toInt(), 0, buffer);
500   if(query->next()) {
501     quError() << "SqliteStorage::getBufferInfo(): received more then one Buffer!";
502     quError() << "         Query:" << query->lastQuery();
503     quError() << "  bound Values:";
504     QList<QVariant> list = query->boundValues().values();
505     for (int i = 0; i < list.size(); ++i)
506       quError() << i << ":" << list.at(i).toString().toAscii().data();
507     Q_ASSERT(false);
508   }
509
510   return bufferInfo;
511 }
512
513 BufferInfo SqliteStorage::getBufferInfo(UserId user, const BufferId &bufferId) {
514   QSqlQuery query(logDb());
515   query.prepare(queryString("select_buffer_by_id"));
516   query.bindValue(":userid", user.toInt());
517   query.bindValue(":bufferid", bufferId.toInt());
518   query.exec();
519   if(!watchQuery(&query))
520     return BufferInfo();
521
522   if(!query.first())
523     return BufferInfo();
524
525   BufferInfo bufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), 0, query.value(4).toString());
526   Q_ASSERT(!query.next());
527
528   return bufferInfo;
529 }
530
531 QList<BufferInfo> SqliteStorage::requestBuffers(UserId user) {
532   QList<BufferInfo> bufferlist;
533   QSqlQuery query(logDb());
534   query.prepare(queryString("select_buffers"));
535   query.bindValue(":userid", user.toInt());
536   
537   query.exec();
538   watchQuery(&query);
539   while(query.next()) {
540     bufferlist << BufferInfo(query.value(0).toInt(), query.value(1).toInt(), (BufferInfo::Type)query.value(2).toInt(), query.value(3).toInt(), query.value(4).toString());
541   }
542   return bufferlist;
543 }
544
545 QList<BufferId> SqliteStorage::requestBufferIdsForNetwork(UserId user, NetworkId networkId) {
546   QList<BufferId> bufferList;
547   QSqlQuery query(logDb());
548   query.prepare(queryString("select_buffers_for_network"));
549   query.bindValue(":networkid", networkId.toInt());
550   query.bindValue(":userid", user.toInt());
551
552   query.exec();
553   watchQuery(&query);
554   while(query.next()) {
555     bufferList << BufferId(query.value(0).toInt());
556   }
557   return bufferList;
558 }
559
560 bool SqliteStorage::removeBuffer(const UserId &user, const BufferId &bufferId) {
561   if(!isValidBuffer(user, bufferId))
562     return false;
563
564   QSqlQuery delBacklogQuery(logDb());
565   delBacklogQuery.prepare(queryString("delete_backlog_for_buffer"));
566   delBacklogQuery.bindValue(":bufferid", bufferId.toInt());
567   delBacklogQuery.exec();
568   if(!watchQuery(&delBacklogQuery))
569     return false;
570
571   QSqlQuery delBufferQuery(logDb());
572   delBufferQuery.prepare(queryString("delete_buffer_for_bufferid"));
573   delBufferQuery.bindValue(":bufferid", bufferId.toInt());
574   delBufferQuery.exec();
575   if(!watchQuery(&delBufferQuery))
576     return false;
577
578   return true;
579 }
580
581 BufferId SqliteStorage::renameBuffer(const UserId &user, const NetworkId &networkId, const QString &newName, const QString &oldName) {
582   // check if such a buffer exists...
583   QSqlQuery existsQuery(logDb());
584   existsQuery.prepare(queryString("select_bufferByName"));
585   existsQuery.bindValue(":networkid", networkId.toInt());
586   existsQuery.bindValue(":userid", user.toInt());
587   existsQuery.bindValue(":buffercname", oldName.toLower());
588   existsQuery.exec();
589   if(!watchQuery(&existsQuery))
590     return false;
591
592   if(!existsQuery.first())
593     return false;
594
595   const int bufferid = existsQuery.value(0).toInt();
596
597   Q_ASSERT(!existsQuery.next());
598
599   // ... and if the new name is still free.
600   existsQuery.bindValue(":networkid", networkId.toInt());
601   existsQuery.bindValue(":userid", user.toInt());
602   existsQuery.bindValue(":buffercname", newName.toLower());
603   existsQuery.exec();
604   if(!watchQuery(&existsQuery))
605     return false;
606
607   if(existsQuery.first())
608     return false;
609
610   QSqlQuery renameBufferQuery(logDb());
611   renameBufferQuery.prepare(queryString("update_buffer_name"));
612   renameBufferQuery.bindValue(":buffername", newName);
613   renameBufferQuery.bindValue(":buffercname", newName.toLower());
614   renameBufferQuery.bindValue(":bufferid", bufferid);
615   renameBufferQuery.exec();
616   if(watchQuery(&existsQuery))
617     return BufferId(bufferid);
618   else
619     return BufferId();
620 }
621
622 void SqliteStorage::setBufferLastSeenMsg(UserId user, const BufferId &bufferId, const MsgId &msgId) {
623   QSqlQuery *query = cachedQuery("update_buffer_lastseen");
624   query->bindValue(":userid", user.toInt());
625   query->bindValue(":bufferid", bufferId.toInt());
626   query->bindValue(":lastseenmsgid", msgId.toInt());
627   query->exec();
628   watchQuery(query);
629 }
630
631 QHash<BufferId, MsgId> SqliteStorage::bufferLastSeenMsgIds(UserId user) {
632   QHash<BufferId, MsgId> lastSeenHash;
633   QSqlQuery query(logDb());
634   query.prepare(queryString("select_buffer_lastseen_messages"));
635   query.bindValue(":userid", user.toInt());
636   query.exec();
637   if(!watchQuery(&query))
638     return lastSeenHash;
639
640   while(query.next()) {
641     lastSeenHash[query.value(0).toInt()] = query.value(1).toInt();
642   }
643   return lastSeenHash;
644 }
645
646 MsgId SqliteStorage::logMessage(Message msg) {
647   QSqlQuery *logMessageQuery = cachedQuery("insert_message");
648   logMessageQuery->bindValue(":time", msg.timestamp().toTime_t());
649   logMessageQuery->bindValue(":bufferid", msg.bufferInfo().bufferId().toInt());
650   logMessageQuery->bindValue(":type", msg.type());
651   logMessageQuery->bindValue(":flags", (int)msg.flags());
652   logMessageQuery->bindValue(":sender", msg.sender());
653   logMessageQuery->bindValue(":message", msg.contents());
654   logMessageQuery->exec();
655   
656   if(logMessageQuery->lastError().isValid()) {
657     // constraint violation - must be NOT NULL constraint - probably the sender is missing...
658     if(logMessageQuery->lastError().number() == 19) {
659       QSqlQuery *addSenderQuery = cachedQuery("insert_sender");
660       addSenderQuery->bindValue(":sender", msg.sender());
661       addSenderQuery->exec();
662       watchQuery(addSenderQuery);
663       logMessageQuery->exec();
664       if(!watchQuery(logMessageQuery))
665         return 0;
666     } else {
667       watchQuery(logMessageQuery);
668     }
669   }
670
671   MsgId msgId = logMessageQuery->lastInsertId().toInt();
672   Q_ASSERT(msgId.isValid());
673   return msgId;
674 }
675
676 QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, int lastmsgs, int offset) {
677   QList<Message> messagelist;
678
679   BufferInfo bufferInfo = getBufferInfo(user, bufferId);
680   if(!bufferInfo.isValid())
681     return messagelist;
682
683   if(offset == -1) {
684     offset = 0;
685   } else {
686     // we have to determine the real offset first
687     QSqlQuery *offsetQuery = cachedQuery("select_messagesOffset");
688     offsetQuery->bindValue(":bufferid", bufferId.toInt());
689     offsetQuery->bindValue(":messageid", offset);
690     offsetQuery->exec();
691     offsetQuery->first();
692     offset = offsetQuery->value(0).toInt();
693   }
694
695   // now let's select the messages
696   QSqlQuery *msgQuery = cachedQuery("select_messages");
697   msgQuery->bindValue(":bufferid", bufferId.toInt());
698   msgQuery->bindValue(":limit", lastmsgs);
699   msgQuery->bindValue(":offset", offset);
700   msgQuery->exec();
701   
702   watchQuery(msgQuery);
703   
704   while(msgQuery->next()) {
705     Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
706                 bufferInfo,
707                 (Message::Type)msgQuery->value(2).toUInt(),
708                 msgQuery->value(5).toString(),
709                 msgQuery->value(4).toString(),
710                 (Message::Flags)msgQuery->value(3).toUInt());
711     msg.setMsgId(msgQuery->value(0).toInt());
712     messagelist << msg;
713   }
714   return messagelist;
715 }
716
717
718 QList<Message> SqliteStorage::requestMsgs(UserId user, BufferId bufferId, QDateTime since, int offset) {
719   QList<Message> messagelist;
720
721   BufferInfo bufferInfo = getBufferInfo(user, bufferId);
722   if(!bufferInfo.isValid())
723     return messagelist;
724
725   // we have to determine the real offset first
726   QSqlQuery *offsetQuery = cachedQuery("select_messagesSinceOffset");
727   offsetQuery->bindValue(":bufferid", bufferId.toInt());
728   offsetQuery->bindValue(":since", since.toTime_t());
729   offsetQuery->exec();
730   offsetQuery->first();
731   offset = offsetQuery->value(0).toInt();
732
733   // now let's select the messages
734   QSqlQuery *msgQuery = cachedQuery("select_messagesSince");
735   msgQuery->bindValue(":bufferid", bufferId.toInt());
736   msgQuery->bindValue(":since", since.toTime_t());
737   msgQuery->bindValue(":offset", offset);
738   msgQuery->exec();
739
740   watchQuery(msgQuery);
741   
742   while(msgQuery->next()) {
743     Message msg(QDateTime::fromTime_t(msgQuery->value(1).toInt()),
744                 bufferInfo,
745                 (Message::Type)msgQuery->value(2).toUInt(),
746                 msgQuery->value(5).toString(),
747                 msgQuery->value(4).toString(),
748                 (Message::Flags)msgQuery->value(3).toUInt());
749     msg.setMsgId(msgQuery->value(0).toInt());
750     messagelist << msg;
751   }
752
753   return messagelist;
754 }
755
756
757 QList<Message> SqliteStorage::requestMsgRange(UserId user, BufferId bufferId, int first, int last) {
758   QList<Message> messagelist;
759
760   BufferInfo bufferInfo = getBufferInfo(user, bufferId);
761   if(!bufferInfo.isValid())
762     return messagelist;
763
764   QSqlQuery *rangeQuery = cachedQuery("select_messageRange");
765   rangeQuery->bindValue(":bufferid", bufferId.toInt());
766   rangeQuery->bindValue(":firstmsg", first);
767   rangeQuery->bindValue(":lastmsg", last);
768   rangeQuery->exec();
769
770   watchQuery(rangeQuery);
771   
772   while(rangeQuery->next()) {
773     Message msg(QDateTime::fromTime_t(rangeQuery->value(1).toInt()),
774                 bufferInfo,
775                 (Message::Type)rangeQuery->value(2).toUInt(),
776                 rangeQuery->value(5).toString(),
777                 rangeQuery->value(4).toString(),
778                 (Message::Flags)rangeQuery->value(3).toUInt());
779     msg.setMsgId(rangeQuery->value(0).toInt());
780     messagelist << msg;
781   }
782
783   return messagelist;
784 }
785
786 QString SqliteStorage::backlogFile() {
787   return quasselDir().absolutePath() + "/quassel-storage.sqlite";  
788 }
789
790
791 // ONLY NEEDED FOR MIGRATION
792 bool SqliteStorage::init(const QVariantMap &settings) {
793   if(!AbstractSqlStorage::init(settings))
794     return false;
795
796   QSqlQuery checkMigratedQuery(logDb());
797   checkMigratedQuery.prepare("SELECT DISTINCT typeOf(password) FROM quasseluser");
798   checkMigratedQuery.exec();
799   if(!watchQuery(&checkMigratedQuery))
800     return false;
801
802   if(!checkMigratedQuery.first())
803     return true;                // table is empty -> no work to be done
804
805   QString passType = checkMigratedQuery.value(0).toString().toLower();
806   if(passType == "text")
807     return true; // allready migrated
808
809   Q_ASSERT(passType == "blob");
810   
811   QSqlQuery getPasswordsQuery(logDb());
812   getPasswordsQuery.prepare("SELECT userid, password FROM quasseluser");
813   getPasswordsQuery.exec();
814
815   if(!watchQuery(&getPasswordsQuery)) {
816     quError() << "unable to migrate to new password format!";
817     return false;
818   }
819
820   QHash<int, QByteArray> passHash;
821   while(getPasswordsQuery.next()) {
822     passHash[getPasswordsQuery.value(0).toInt()] = getPasswordsQuery.value(1).toByteArray();
823   }
824
825   QSqlQuery setPasswordsQuery(logDb());
826   setPasswordsQuery.prepare("UPDATE quasseluser SET password = :password WHERE userid = :userid");
827   foreach(int userId, passHash.keys()) {
828     setPasswordsQuery.bindValue(":password", QString(passHash[userId]));
829     setPasswordsQuery.bindValue(":userid", userId);
830     setPasswordsQuery.exec();
831     watchQuery(&setPasswordsQuery);
832   }
833
834   quDebug() << "successfully migrated passwords!";
835   return true;
836 }