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